From c18f11b0d1e989215dbd05221e763ef6c0208fc9 Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Thu, 27 Aug 2026 17:19:18 -0700 Subject: [PATCH 001/103] [WebRTC] Out-of-bounds write in copyVideoFrameBuffer for odd-width I420/I010 frames Reviewed by Jean-Yves Avenard. copyVideoFrameBuffer used i420Frame->width() / i010Frame->width() as both the destination row pitch and the UV plane size. libyuv's I420ToNV12 / I010ToP010 round chroma dimensions up: they emit ((width + 1) / 2) * 2 bytes per UV row and (height + 1) / 2 UV rows. For odd width, libyuv writes one byte (I420) or two bytes (I010) past each UV row, with the final row's overrun landing past the end of the SharedMemory mapping. For odd height, an entire UV row's worth of data (~width or 2*width bytes) lands past the mapping. Round chroma dimensions up to match libyuv. SharedVideoFrameInfo::fromVideoFrameBuffer now sizes the UV plane via computeStrideUV(frame) (sizeof(byteType) * (width rounded up to even)) and computeHeightUV(frame) ((height + 1) / 2). copyVideoFrameBuffer applies the same rounding to the strideUV it hands to libyuv, so writer row pitch matches the receiver's m_bytesPerRowPlaneB and rows no longer overlap. Re-land 319878@main, reverted by 319927@main for breaking ASan builds. The added tests make TestWebKitAPI a libwebrtc client, but its Configurations/Base.xcconfig searched neither the webrtc header directory nor the directory libwebrtc.dylib installs into, so the build failed first at `api/scoped_refptr.h` and then at `-lwebrtc`. Give it both search paths, so it resolves libwebrtc the same way WebCore.xcconfig and WebKit.xcconfig already do. Covered by added API tests. * Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp: * Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig: * Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm: (webrtc::copyVideoFrameBuffer): * Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm: (WebCore::computeStrideY): (WebCore::computeStrideUV): (WebCore::computeWidthUV): (WebCore::computeHeightUV): (WebCore::SharedVideoFrameInfo::fromVideoFrameBuffer): * Tools/TestWebKitAPI/Configurations/Base.xcconfig: * Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig: * Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm: (TestWebKitAPI::TEST(SharedVideoFrame, PlaneAlphaSize)): (TestWebKitAPI::testSharedVideoFrameInfoRoundTrip): (TestWebKitAPI::TEST(SharedVideoFrame, OddWidthI420)): (TestWebKitAPI::TEST(SharedVideoFrame, OddHeightI420)): (TestWebKitAPI::TEST(SharedVideoFrame, OddWidthAndHeightI420)): (TestWebKitAPI::TEST(SharedVideoFrame, OddWidthI010)): (TestWebKitAPI::TEST(SharedVideoFrame, OddHeightI010)): (TestWebKitAPI::TEST(SharedVideoFrame, OddWidthAndHeightI010)): (TestWebKitAPI::TEST(WebCore, SharedVideoFramePlaneAlphaSize)): Deleted. Originally-landed-as: 305413.997@safari-7624.5-branch (881cd542f818). rdar://185368288 Canonical link: https://commits.webkit.org/320009@main --- .../libwebrtc/Configurations/libwebrtc.exp | 5 + .../Configurations/libwebrtc.xcconfig | 2 +- .../webkit_sdk/WebKit/WebKitUtilities.mm | 4 +- .../platform/cocoa/SharedVideoFrameInfo.mm | 52 +++++-- .../Configurations/Base.xcconfig | 4 +- .../Configurations/TestWebKitAPIBase.xcconfig | 10 +- .../Tests/WebCore/cocoa/SharedVideoFrame.mm | 130 +++++++++++++++++- 7 files changed, 189 insertions(+), 18 deletions(-) diff --git a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp index 8b1c8e0f22e8..1abac82e44bb 100644 --- a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp +++ b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp @@ -1,6 +1,8 @@ +__ZN6webrtc10I010Buffer6CreateEii __ZN6webrtc10I420Buffer12MutableDataUEv __ZN6webrtc10I420Buffer12MutableDataVEv __ZN6webrtc10I420Buffer12MutableDataYEv +__ZN6webrtc10I420Buffer6CreateEii __ZN6webrtc10I420Buffer6RotateERKNS_19I420BufferInterfaceENS_13VideoRotationE __ZN6webrtc10I420Buffer8SetBlackEPS0_ __ZN6webrtc10VideoFrameD1Ev @@ -426,3 +428,6 @@ __ZNK6webrtc18VideoFrameMetadata15GetDependenciesEv __ZNK6webrtc8RtpCodec9mime_typeEv __ZN6webrtc14SdpVideoFormatC1ENSt3__117basic_string_viewIcNS1_11char_traitsIcEEEERKNS_17CodecParameterMapE __ZN4absl19ThrowStdLengthErrorEPKc +__ZN6webrtc10I010Buffer12MutableDataUEv +__ZN6webrtc10I010Buffer12MutableDataVEv +__ZN6webrtc10I010Buffer12MutableDataYEv diff --git a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig index 364eae747465..417e25c009a7 100644 --- a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig +++ b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig @@ -90,6 +90,6 @@ OTHER_LDFLAGS = $(inherited) $(SOURCE_VERSION_LDFLAGS) $(WEBRTC_LDFLAGS_ENABLE_L // Allow fuzzers to link to libwebrtc.dylib. WEBRTC_ALLOWABLE_CLIENTS = $(WEBRTC_ALLOWABLE_CLIENTS_$(WK_NOT_$(ENABLE_LIBFUZZER))); -WEBRTC_ALLOWABLE_CLIENTS_YES = -allowable_client WebCore -allowable_client WebCoreTestSupport -allowable_client WebKit; +WEBRTC_ALLOWABLE_CLIENTS_YES = -allowable_client WebCore -allowable_client WebCoreTestSupport -allowable_client WebKit -allowable_client TestWebKitAPI; WARNING_CFLAGS = $(inherited) -Wno-nullability-completeness; diff --git a/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm b/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm index f5631db00b03..d5342a5ad11b 100644 --- a/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm +++ b/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm @@ -436,7 +436,7 @@ bool copyVideoFrameBuffer(VideoFrameBuffer& buffer, uint8_t* data) auto* i420Frame = buffer.GetI420(); auto* dataY = data; auto strideY = i420Frame->width(); - auto strideUV = i420Frame->width(); + auto strideUV = i420Frame->width() & 1 ? i420Frame->width() + 1 : i420Frame->width(); auto* dataUV = data + (i420Frame->width() * i420Frame->height()); return !libyuv::I420ToNV12(i420Frame->DataY(), i420Frame->StrideY(), i420Frame->DataU(), i420Frame->StrideU(), @@ -448,7 +448,7 @@ bool copyVideoFrameBuffer(VideoFrameBuffer& buffer, uint8_t* data) auto* i010Frame = buffer.GetI010(); auto* dataY = reinterpret_cast(data); auto strideY = i010Frame->width(); - auto strideUV = i010Frame->width(); + auto strideUV = i010Frame->width() & 1 ? i010Frame->width() + 1 : i010Frame->width(); auto* dataUV = dataY + (i010Frame->width() * i010Frame->height()); return !libyuv::I010ToP010(i010Frame->DataY(), i010Frame->StrideY(), i010Frame->DataU(), i010Frame->StrideU(), diff --git a/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm b/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm index a7ff7a820a30..15d0433ec029 100644 --- a/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm +++ b/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm @@ -281,23 +281,53 @@ } #if USE(LIBWEBRTC) +template +uint32_t computeStrideY(const webrtc::VideoFrameBuffer& frame) +{ + auto width = static_cast(frame.width()); + return sizeof(byteType) * width; +} + +template +uint32_t computeStrideUV(const webrtc::VideoFrameBuffer& frame) +{ + auto width = static_cast(frame.width()); + return sizeof(byteType) * (width & 1 ? width + 1 : width); +} + +static uint32_t computeWidthUV(const webrtc::VideoFrameBuffer& frame) +{ + auto width = static_cast(frame.width()); + return (width + 1) / 2; +} + +static uint32_t computeHeightUV(const webrtc::VideoFrameBuffer& frame) +{ + auto height = static_cast(frame.height()); + return (height + 1) / 2; +} + SharedVideoFrameInfo SharedVideoFrameInfo::fromVideoFrameBuffer(const webrtc::VideoFrameBuffer& frame) { if (frame.type() == webrtc::VideoFrameBuffer::Type::kNative) return SharedVideoFrameInfo { }; auto type = frame.type(); - if (type == webrtc::VideoFrameBuffer::Type::kI420) - return SharedVideoFrameInfo { kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, - static_cast(frame.width()), static_cast(frame.height()), static_cast(frame.width()), - static_cast(frame.width()) / 2, static_cast(frame.height()) / 2, static_cast(frame.width()) }; - - if (type == webrtc::VideoFrameBuffer::Type::kI010) - return SharedVideoFrameInfo { kCVPixelFormatType_420YpCbCr10BiPlanarFullRange, - static_cast(frame.width()), static_cast(frame.height()), static_cast(frame.width() * 2), - static_cast(frame.width()) / 2, static_cast(frame.height()) / 2, static_cast(frame.width()) * 2 }; - - return SharedVideoFrameInfo { }; + if (type == webrtc::VideoFrameBuffer::Type::kI420) { + return { + kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, + static_cast(frame.width()), static_cast(frame.height()), computeStrideY(frame), + computeWidthUV(frame), computeHeightUV(frame), computeStrideUV(frame) + }; + } + if (type == webrtc::VideoFrameBuffer::Type::kI010) { + return { + kCVPixelFormatType_420YpCbCr10BiPlanarFullRange, + static_cast(frame.width()), static_cast(frame.height()), computeStrideY(frame), + computeWidthUV(frame), computeHeightUV(frame), computeStrideUV(frame) + }; + } + return { }; } bool SharedVideoFrameInfo::writeVideoFrameBuffer(webrtc::VideoFrameBuffer& frameBuffer, std::span data) diff --git a/Tools/TestWebKitAPI/Configurations/Base.xcconfig b/Tools/TestWebKitAPI/Configurations/Base.xcconfig index fa2529f333e7..40779526a7bc 100644 --- a/Tools/TestWebKitAPI/Configurations/Base.xcconfig +++ b/Tools/TestWebKitAPI/Configurations/Base.xcconfig @@ -48,10 +48,10 @@ PROJECT_HEADER_SEARCH_PATHS_cocoatouch = $(inherited) $(SRCROOT)/../../Source/We SWIFT_INCLUDE_PATHS = $(inherited) $(SWIFT_INCLUDE_PATHS_$(USE_INTERNAL_SDK)); SWIFT_INCLUDE_PATHS_ = $(SRCROOT)/../../Source/WebKit/Platform/spi/Cocoa; -HEADER_SEARCH_PATHS = $(ALTERNATE_HEADER_SEARCH_PATHS) ${BUILT_PRODUCTS_DIR}/usr/local/include $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders $(BUILT_PRODUCTS_DIR)/WebKitTestSupport $(WEBKIT_TESTSUPPORT_INSTALLED_DIR) ${SRCROOT} $(PROJECT_HEADER_SEARCH_PATHS); +HEADER_SEARCH_PATHS = $(ALTERNATE_HEADER_SEARCH_PATHS) ${BUILT_PRODUCTS_DIR}/usr/local/include $(LIBWEBRTC_HEADER_SEARCH_PATHS) $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders $(BUILT_PRODUCTS_DIR)/WebKitTestSupport $(WEBKIT_TESTSUPPORT_INSTALLED_DIR) ${SRCROOT} $(PROJECT_HEADER_SEARCH_PATHS); WEBKIT_TESTSUPPORT_INSTALLED_DIR[config=Production] = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_HEADERS_FOLDER_PATH)/WebKitTestSupport; -LIBRARY_SEARCH_PATHS = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_INSTALL_PATH) $(inherited); +LIBRARY_SEARCH_PATHS = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_INSTALL_PATH) "$(SDK_DIR)$(WEBCORE_LIBRARY_DIR)" $(inherited); SYSTEM_HEADER_SEARCH_PATHS = $(inherited) $(WK_PRIVATE_SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_HEADERS_FOLDER_PATH); SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) $(SYSTEM_FRAMEWORK_SEARCH_PATHS_$(WK_USE_ALTERNATE_FRAMEWORKS_DIR)) $(SYSTEM_FRAMEWORK_SEARCH_PATHS_WEBCORE_$(WK_COCOA_TOUCH)); diff --git a/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig b/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig index dcf80fd688a0..48cff520e661 100644 --- a/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig +++ b/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig @@ -114,7 +114,15 @@ WK_GAMECONTROLLER_LDFLAGS_iphonesimulator = -framework GameController WK_GAMECONTROLLER_LDFLAGS_xros = -framework GameController WK_GAMECONTROLLER_LDFLAGS_xrsimulator = -framework GameController -OTHER_LDFLAGS = $(inherited) $(GTEST_LDFLAGS) -lxml2 -force_load $(BUILT_PRODUCTS_DIR)/libTestWebKitAPI.a -framework JavaScriptCore -framework WebCore -framework WebKit -lWebCoreTestSupport -framework Metal -framework IOSurface $(WK_APPSERVERSUPPORT_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) -framework Network -framework UniformTypeIdentifiers -framework CoreFoundation -framework CoreServices -framework CFNetwork -framework CoreGraphics -framework CoreLocation -framework CoreText -framework IOKit -lboringssl -licucore -framework LocalAuthentication -framework QuartzCore -framework Security -framework AVKit $(WK_BROWSERENGINEKIT_LDFLAGS) $(WK_HID_LDFLAGS) $(WK_IMAGEIO_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SYSTEM_LDFLAGS) $(WK_UIKITMACHELPER_LDFLAGS) $(WK_VISIONKITCORE_LDFLAGS) $(WK_WEBCORE_LDFLAGS) $(WK_REVEAL_LDFLAGS) $(WK_WRITING_TOOLS_LDFLAGS) $(WK_WRITING_TOOLS_UI_LDFLAGS) $(WK_GAMECONTROLLER_LDFLAGS) $(OTHER_LDFLAGS_DELAY_INIT) $(OTHER_LDFLAGS_PLATFORM_$(WK_COCOA_TOUCH)) $(OTHER_LDFLAGS_ENTITLEMENTS); +// See ENABLE_WEB_RTC in FeatureDefines.xcconfig in WebCore and WebKit. +WK_LIBWEBRTC_LDFLAGS = $(WK_LIBWEBRTC_LDFLAGS_$(WK_PLATFORM_NAME)) +WK_LIBWEBRTC_LDFLAGS_iphoneos = -weak-lwebrtc +WK_LIBWEBRTC_LDFLAGS_iphonesimulator = -weak-lwebrtc +WK_LIBWEBRTC_LDFLAGS_macosx = -weak-lwebrtc +WK_LIBWEBRTC_LDFLAGS_xros = -weak-lwebrtc +WK_LIBWEBRTC_LDFLAGS_xrsimulator = -weak-lwebrtc + +OTHER_LDFLAGS = $(inherited) $(GTEST_LDFLAGS) -lxml2 -force_load $(BUILT_PRODUCTS_DIR)/libTestWebKitAPI.a -framework JavaScriptCore -framework WebCore -framework WebKit -lWebCoreTestSupport -framework Metal -framework IOSurface $(WK_APPSERVERSUPPORT_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) -framework Network -framework UniformTypeIdentifiers -framework CoreFoundation -framework CoreServices -framework CFNetwork -framework CoreGraphics -framework CoreLocation -framework CoreText -framework IOKit -lboringssl -licucore -framework LocalAuthentication -framework QuartzCore -framework Security -framework AVKit $(WK_BROWSERENGINEKIT_LDFLAGS) $(WK_HID_LDFLAGS) $(WK_IMAGEIO_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SYSTEM_LDFLAGS) $(WK_UIKITMACHELPER_LDFLAGS) $(WK_VISIONKITCORE_LDFLAGS) $(WK_WEBCORE_LDFLAGS) $(WK_REVEAL_LDFLAGS) $(WK_WRITING_TOOLS_LDFLAGS) $(WK_WRITING_TOOLS_UI_LDFLAGS) $(WK_GAMECONTROLLER_LDFLAGS) $(WK_LIBWEBRTC_LDFLAGS) $(OTHER_LDFLAGS_DELAY_INIT) $(OTHER_LDFLAGS_PLATFORM_$(WK_COCOA_TOUCH)) $(OTHER_LDFLAGS_ENTITLEMENTS) -Xlinker --no-demangle; OTHER_LDFLAGS_DELAY_INIT[sdk=iphone*] = -Wl,-delay_framework,CoreTelephony; OTHER_LDFLAGS_DELAY_INIT[sdk=appletv*] = ; diff --git a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm index fd95a943ada2..336f80891ce7 100644 --- a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm +++ b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm @@ -29,9 +29,16 @@ #import #include +#if USE(LIBWEBRTC) +WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN +#include +#include +WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END +#endif + namespace TestWebKitAPI { -TEST(WebCore, SharedVideoFramePlaneAlphaSize) +TEST(SharedVideoFrame, PlaneAlphaSize) { Vector data(128); WebCore::SharedVideoFrameInfo info { @@ -54,4 +61,125 @@ EXPECT_FALSE(info2); } +#if USE(LIBWEBRTC) + +template +static void testSharedVideoFrameInfoRoundTrip(Buffer& buffer) +{ + static_assert(sizeof(SampleType) == 1 || sizeof(SampleType) == 2); + + const int width = buffer.width(); + const int height = buffer.height(); + const int chromaWidth = (width + 1) / 2; + const int chromaHeight = (height + 1) / 2; + + // I420 stores 8-bit samples; I010 stores 10-bit samples in uint16_t. + constexpr unsigned valueMask = sizeof(SampleType) == 1 ? 0xFFu : 0x3FFu; + constexpr unsigned msbShift = sizeof(SampleType) == 1 ? 0u : 6u; + + auto fillPlane = [&](std::span plane, int stride, int planeWidth, int planeHeight, unsigned seed) { + for (int y = 0; y < planeHeight; ++y) { + auto row = plane.subspan(y * stride, planeWidth); + for (int x = 0; x < planeWidth; ++x) + row[x] = static_cast((seed + 31u * y + 7u * x) & valueMask); + } + }; + fillPlane(unsafeMakeSpan(buffer.MutableDataY(), buffer.StrideY() * height), buffer.StrideY(), width, height, 1); + fillPlane(unsafeMakeSpan(buffer.MutableDataU(), buffer.StrideU() * chromaHeight), buffer.StrideU(), chromaWidth, chromaHeight, 101); + fillPlane(unsafeMakeSpan(buffer.MutableDataV(), buffer.StrideV() * chromaHeight), buffer.StrideV(), chromaWidth, chromaHeight, 211); + + auto info = WebCore::SharedVideoFrameInfo::fromVideoFrameBuffer(buffer); + EXPECT_GT(info.storageSize(), 0u); + + constexpr size_t guardSize = 64; + constexpr uint8_t guardByte = 0xCD; + + Vector storage(info.storageSize() + guardSize); + auto allBytes = storage.mutableSpan(); + auto frameBytes = allBytes.subspan(0, info.storageSize()); + auto guardBytes = allBytes.subspan(info.storageSize()); + for (auto& b : guardBytes) + b = guardByte; + + EXPECT_TRUE(info.writeVideoFrameBuffer(buffer, frameBytes)); + + for (auto byte : guardBytes) + EXPECT_EQ(byte, guardByte); + + auto payload = spanReinterpretCast(frameBytes.subspan(sizeof(WebCore::SharedVideoFrameInfo))); + const int outStrideY = width; + const int outStrideUV = (width & 1) ? width + 1 : width; + auto outY = payload.first(outStrideY * height); + auto outUV = payload.subspan(outStrideY * height, outStrideUV * chromaHeight); + + auto srcY = unsafeMakeSpan(buffer.DataY(), buffer.StrideY() * height); + auto srcU = unsafeMakeSpan(buffer.DataU(), buffer.StrideU() * chromaHeight); + auto srcV = unsafeMakeSpan(buffer.DataV(), buffer.StrideV() * chromaHeight); + + for (int y = 0; y < height; ++y) { + auto srcRow = srcY.subspan(y * buffer.StrideY(), width); + auto outRow = outY.subspan(y * outStrideY, width); + for (int x = 0; x < width; ++x) { + auto expected = static_cast(static_cast(srcRow[x]) << msbShift); + EXPECT_EQ(outRow[x], expected) << "Y mismatch at (" << x << ", " << y << ")"; + } + } + + for (int y = 0; y < chromaHeight; ++y) { + auto srcURow = srcU.subspan(y * buffer.StrideU(), chromaWidth); + auto srcVRow = srcV.subspan(y * buffer.StrideV(), chromaWidth); + auto outRow = outUV.subspan(y * outStrideUV, 2 * chromaWidth); + for (int x = 0; x < chromaWidth; ++x) { + auto expectedU = static_cast(static_cast(srcURow[x]) << msbShift); + auto expectedV = static_cast(static_cast(srcVRow[x]) << msbShift); + EXPECT_EQ(outRow[2 * x], expectedU) << "U mismatch at (" << x << ", " << y << ")"; + EXPECT_EQ(outRow[2 * x + 1], expectedV) << "V mismatch at (" << x << ", " << y << ")"; + } + } +} + +TEST(SharedVideoFrame, OddWidthI420) +{ + auto buffer = webrtc::I420Buffer::Create(681, 1280); + ASSERT_TRUE(buffer); + testSharedVideoFrameInfoRoundTrip(*buffer); +} + +TEST(SharedVideoFrame, OddHeightI420) +{ + auto buffer = webrtc::I420Buffer::Create(680, 15); + ASSERT_TRUE(buffer); + testSharedVideoFrameInfoRoundTrip(*buffer); +} + +TEST(SharedVideoFrame, OddWidthAndHeightI420) +{ + auto buffer = webrtc::I420Buffer::Create(681, 15); + ASSERT_TRUE(buffer); + testSharedVideoFrameInfoRoundTrip(*buffer); +} + +TEST(SharedVideoFrame, OddWidthI010) +{ + auto buffer = webrtc::I010Buffer::Create(681, 1280); + ASSERT_TRUE(buffer); + testSharedVideoFrameInfoRoundTrip(*buffer); +} + +TEST(SharedVideoFrame, OddHeightI010) +{ + auto buffer = webrtc::I010Buffer::Create(680, 15); + ASSERT_TRUE(buffer); + testSharedVideoFrameInfoRoundTrip(*buffer); +} + +TEST(SharedVideoFrame, OddWidthAndHeightI010) +{ + auto buffer = webrtc::I010Buffer::Create(681, 15); + ASSERT_TRUE(buffer); + testSharedVideoFrameInfoRoundTrip(*buffer); +} + +#endif // USE(LIBWEBRTC) + }; // namespace TestWebKitAPI From 92e0ba83201799cf2b06703b37758746c091eeea Mon Sep 17 00:00:00 2001 From: Issac Roy Date: Thu, 27 Aug 2026 17:57:03 -0700 Subject: [PATCH 002/103] [webkitbugspy] Cannot remove a relation between two issues https://bugs.webkit.org/show_bug.cgi?id=322308 rdar://problem/185553192 Reviewed by Sam Sneddon. Relations could only be added. Bugzilla's 'relate' sent '{"add": [...]}' and radar's created a relationship, with no way to undo either, so a tool which records a dependency between two issues cannot later forget it and a stale relation has to be removed by hand. Add 'unrelate', which Bugzilla implements by sending '{"remove": [...]}' through the request 'relate' already builds and radar by deleting a relationship the radar already holds. 'duplicate-of' and 'original-of' are not removable this way, since they are recorded as a resolution rather than as a relationship. * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py: (Tracker): * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py: (Tracker): (Tracker._modify_relations): (Tracker.relate): (Tracker.unrelate): * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py: (Issue.unrelate): * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py: (Bugzilla._issue): (Bugzilla._issue.related_for): * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py: (RadarModel.__init__): (RadarModel.commit_changes): (RadarModel.delete_relationship): * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py: (Tracker.remove_relationship): (Tracker): (Tracker.unrelate): (Tracker.relation_key): * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py: (test_relate_simple): * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py: * Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py: (Tracker.unrelate): (Tracker): (Tracker.relation_key): Canonical link: https://commits.webkit.org/320010@main --- .../webkitbugspy/webkitbugspy/__init__.py | 2 +- .../webkitbugspy/webkitbugspy/bugzilla.py | 53 +++++++++--------- .../webkitbugspy/webkitbugspy/issue.py | 3 + .../webkitbugspy/mocks/bugzilla.py | 37 ++++++++++--- .../webkitbugspy/webkitbugspy/mocks/radar.py | 15 +++++ .../webkitbugspy/webkitbugspy/radar.py | 55 +++++++++++++++++++ .../webkitbugspy/tests/bugzilla_unittest.py | 7 +++ .../webkitbugspy/tests/radar_unittest.py | 5 ++ .../webkitbugspy/webkitbugspy/tracker.py | 3 + 9 files changed, 145 insertions(+), 35 deletions(-) diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py index 8b6e4e0ab989..25226b923597 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py @@ -50,7 +50,7 @@ def _maybe_add_library_path(path): "See https://github.com/WebKit/WebKit/tree/main/Tools/Scripts/libraries/webkitcorepy" ) -version = Version(0, 15, 4) +version = Version(0, 15, 5) from .user import User from .issue import Issue diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py index 41828dd5e7d4..d4b7a42053c6 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py @@ -596,55 +596,58 @@ def add_comment(self, issue, text): return result + RELATIONS = ('depends_on', 'blocks', 'regressed_by', 'regressions') + def related_issue_id(self, issue): if isinstance(issue.tracker, Tracker): return issue.id else: raise TypeError('Cannot relate issues of different types.') - def relate(self, issue, depends_on=None, blocks=None, regressed_by=None, regressions=None, **relations): - if relations: - raise TypeError("'{}' is an invalid relation".format(list(relations.keys())[0])) + def _modify_relations(self, issue, action, relations): + if invalid := [relation for relation in relations if relation not in self.RELATIONS]: + raise TypeError(f"'{invalid[0]}' is an invalid relation") - update_dict = dict() - update_dict['ids'] = [issue.id] - if depends_on: - update_dict['depends_on'] = {'add': [self.related_issue_id(depends_on)]} - if blocks: - update_dict['blocks'] = {'add': [self.related_issue_id(blocks)]} - if regressed_by: - update_dict['regressed_by'] = {'add': [self.related_issue_id(regressed_by)]} - if regressions: - update_dict['regressions'] = {'add': [self.related_issue_id(regressions)]} + update_dict = {'ids': [issue.id]} + for relation, related in relations.items(): + if related: + update_dict[relation] = {action: [self.related_issue_id(related)]} response = None try: response = self.session.put( - '{}/rest/bug/{}{}'.format(self.url, issue.id, self._login_arguments(required=True)), + f'{self.url}/rest/bug/{issue.id}{self._login_arguments(required=True)}', json=update_dict, timeout=self.timeout, ) except requests.exceptions.RequestException as e: - sys.stderr.write('Request Error: {}\n'.format(e)) + sys.stderr.write(f'Request Error: {e}\n') if response is not None and response.status_code // 100 == 4 and self._logins_left: self._logins_left -= 1 if response is None or response.status_code // 100 != 2: - sys.stderr.write("Failed to modify '{}'\n".format(issue)) + sys.stderr.write(f"Failed to modify '{issue}'\n") return None if not issue._related: self.populate(issue, 'related') - else: - if depends_on: - issue._related['depends_on'].append(depends_on) - if blocks: - issue._related['blocks'].append(blocks) - if regressed_by: - issue._related['regressed_by'].append(regressed_by) - if regressions: - issue._related['regressions'].append(regressions) + return issue + + for relation, related in relations.items(): + if not related: + continue + existing = issue._related.setdefault(relation, []) + if action == 'add' and related not in existing: + existing.append(related) + elif action == 'remove' and related in existing: + existing.remove(related) return issue + def relate(self, issue, **relations): + return self._modify_relations(issue, 'add', relations) + + def unrelate(self, issue, **relations): + return self._modify_relations(issue, 'remove', relations) + @property @webkitcorepy.decorators.Memoize() def projects(self): diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py index 253abefc8421..84e8822b4df5 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py @@ -206,6 +206,9 @@ def related(self): def relate(self, **relations): return self.tracker.relate(self, **relations) + def unrelate(self, **relations): + return self.tracker.unrelate(self, **relations) + @property def assignee(self): if self._assignee is None: diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py index 95395aea2618..48e59e26b8d6 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py @@ -158,18 +158,37 @@ def _issue(self, url, id, credentials=None, data=None): issue['component'] = data['component'] if data.get('version'): issue['version'] = data['version'] - issue['related'] = {'blocks': [], 'depends_on': [], 'regressions': [], 'regressed_by': []} - if data.get('depends_on'): - issue['related']['depends_on'] = data['depends_on'] - if data.get('blocks'): - issue['related']['blocks'] = data['blocks'] - if data.get('regressed_by'): - issue['related']['regressed_by'] = data['regressed_by'] - if data.get('regressions'): - issue['related']['regressions'] = data['regressions'] if data.get('see_also'): issue['related_links'] = data['see_also']['add'] + # A dependency is one edge, so recording it on this issue also records it on the other + INVERSE = { + 'depends_on': 'blocks', + 'blocks': 'depends_on', + 'regressed_by': 'regressions', + 'regressions': 'regressed_by', + } + + def related_for(number, relation): + related = self.issues[number].setdefault('related', {key: [] for key in INVERSE}) + return related.setdefault(relation, []) + + for relation, inverse in INVERSE.items(): + if not (change := data.get(relation)): + continue + for other in change.get('add') or []: + forward, reverse = related_for(id, relation), related_for(other, inverse) + if other not in forward: + forward.append(other) + if id not in reverse: + reverse.append(id) + for other in change.get('remove') or []: + forward, reverse = related_for(id, relation), related_for(other, inverse) + if other in forward: + forward.remove(other) + if id in reverse: + reverse.remove(id) + keywords = data.get('keywords', {}) if keywords: assert len(keywords) == 1 diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py index c47c92d1bbc4..c68742e19902 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py @@ -176,6 +176,7 @@ def __init__(self, client, issue, additional_fields=None): self.state = 'Analyze' if issue['opened'] else 'Verify' self.duplicateOfProblemID = issue['original']['id'] if issue.get('original', None) else None self.related = list() + self.unrelated = list() if issue.get('substate'): self.substate = issue['substate'] else: @@ -288,6 +289,17 @@ def commit_changes(self): self.client.parent.issues[r.related_radar_id]['related'] = list() self.client.parent.issues[r.related_radar_id]['related'].append(inverse_r_dict) + for r in list(self.unrelated): + r_dict = {'relationship': r.type, 'related_radar': r.related_radar_id} + entries = self.client.parent.issues[self.id].get('related') or [] + if r_dict in entries: + entries.remove(r_dict) + + inverse_r_dict = {'relationship': Radar.Relationship.inverse_map[r.type], 'related_radar': self.id} + entries = self.client.parent.issues[r.related_radar_id].get('related') or [] + if inverse_r_dict in entries: + entries.remove(inverse_r_dict) + if getattr(self, 'sourceChanges', None): self.client.parent.issues[self.id]['sourceChanges'] = self.sourceChanges @@ -314,6 +326,9 @@ def relationships(self, relationships=None): def add_relationship(self, relationship): self.related.append(relationship) + def delete_relationship(self, relationship): + self.unrelated.append(relationship) + def remove_keyword(self, keyword): if keyword.name in self._issue.get('keywords') or []: self._issue['keywords'].remove(keyword.name) diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py index 2dfca173c59f..9f8f1baa8cd8 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py @@ -612,6 +612,61 @@ def create_relationship(self, issue, issue2, relationship): return None @handle_access_exception + def remove_relationship(self, issue, issue2, relationship): + if relationship not in self.RELATIONSHIP_TYPES: + sys.stderr.write(f'{relationship} is not a valid relationship type.') + return None + if relationship in ( + self.radarclient().Relationship.TYPE_DUPLICATE_OF, + self.radarclient().Relationship.TYPE_ORIGINAL_OF, + ): + raise NotImplementedError(f'Cannot remove a {relationship} relationship') + + radar = self.client.radar_for_id(issue.id) + if not radar: + sys.stderr.write(f"Failed to fetch '{issue.link}'\n") + return None + + # 'delete_relationship' only accepts a relationship the radar already has + existing = next(( + candidate for candidate in radar.relationships() or [] + if candidate.type == relationship and candidate.related_radar_id == issue2.id + ), None) + if not existing: + return issue + + radar.delete_relationship(existing) + radar.commit_changes() + + if not issue._related: + self.populate(issue, 'related') + else: + issue._related[relationship] = [ + candidate for candidate in issue._related[relationship] if candidate.id != issue2.id + ] + return issue + + @handle_access_exception + def unrelate(self, issue, related_to=None, blocked_by=None, blocking=None, parent_of=None, subtask_of=None, + cause_of=None, caused_by=None, duplicate_of=None, original_of=None, **relations): + if relations: + raise TypeError(f"'{list(relations.keys())[0]}' is an invalid relation") + + for related, relationship in ( + (related_to, self.radarclient().Relationship.TYPE_RELATED_TO), + (blocked_by, self.radarclient().Relationship.TYPE_BLOCKED_BY), + (blocking, self.radarclient().Relationship.TYPE_BLOCKING), + (parent_of, self.radarclient().Relationship.TYPE_PARENT_OF), + (subtask_of, self.radarclient().Relationship.TYPE_SUBTASK_OF), + (cause_of, self.radarclient().Relationship.TYPE_CAUSE_OF), + (caused_by, self.radarclient().Relationship.TYPE_CAUSED_BY), + (duplicate_of, self.radarclient().Relationship.TYPE_DUPLICATE_OF), + (original_of, self.radarclient().Relationship.TYPE_ORIGINAL_OF), + ): + if related: + self.remove_relationship(issue, related, relationship) + return issue + def relate(self, issue, related_to=None, blocked_by=None, blocking=None, parent_of=None, subtask_of=None, cause_of=None, caused_by=None, duplicate_of=None, original_of=None, **relations): if relations: diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py index 216f7b900d22..a983fc1e6130 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py @@ -1135,6 +1135,13 @@ def test_relate_simple(self): self.assertEqual(issue.related['depends_on'], [tracker.issue(2)]) self.assertEqual(issue.related['blocks'], []) + # The edge runs both ways + self.assertEqual(tracker.issue(2).related['blocks'], [issue]) + + issue.unrelate(depends_on=tracker.issue(2)) + self.assertEqual(issue.related['depends_on'], []) + self.assertEqual(tracker.issue(2).related['blocks'], []) + def test_relate(self): with mocks.Bugzilla(self.URL.split('://')[1], environment=wkmocks.Environment( BUGS_EXAMPLE_COM_USERNAME='tcontributor@example.com', diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py index da95f30603f0..d73cbfe8da24 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py @@ -596,6 +596,11 @@ def test_relate_inverse(self): self.assertEqual(tracker.issue(1).related['blocking'], [tracker.issue(2)]) self.assertEqual(tracker.issue(2).related['blocked-by'], [tracker.issue(1)]) + # Removing it removes both ends, as adding it added both + tracker.issue(2).unrelate(blocked_by=tracker.issue(1)) + self.assertEqual(tracker.issue(2).related['blocked-by'], []) + self.assertEqual(tracker.issue(1).related['blocking'], []) + tracker.issue(2).relate(parent_of=tracker.issue(3)) self.assertEqual(tracker.issue(2).related['parent-of'], [tracker.issue(3)]) self.assertEqual(tracker.issue(3).related['subtask-of'], [tracker.issue(2)]) diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py index 124e61c63b0c..957661cd24e9 100644 --- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py +++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py @@ -183,6 +183,9 @@ def set(self, issue, **properties): def relate(self, issue, **relations): raise NotImplementedError() + def unrelate(self, issue, **relations): + raise NotImplementedError() + def add_comment(self, issue, text): raise NotImplementedError() From 585cb45609b37a90566b0b114c25043c7f3f6680 Mon Sep 17 00:00:00 2001 From: Dominic Mazzoni Date: Thu, 27 Aug 2026 17:58:29 -0700 Subject: [PATCH 003/103] AX: AXTextMarkerRangeForUIElement covers a native text control as a replaced object rather than its value https://bugs.webkit.org/show_bug.cgi?id=322688 rdar://185960907 Reviewed by Tyler Wilcock. On the live AX tree, asking for a + + +
alpha
bravo
+A cake + + + + diff --git a/LayoutTests/accessibility/mac/text-marker-range-for-text-control-expected.txt b/LayoutTests/accessibility/mac/text-marker-range-for-text-control-expected.txt new file mode 100644 index 000000000000..8dfd3e1f2130 --- /dev/null +++ b/LayoutTests/accessibility/mac/text-marker-range-for-text-control-expected.txt @@ -0,0 +1,31 @@ +Asserts that AXTextMarkerRangeForUIElement covers a text control's value rather than the +control as a single replaced object. A genuinely replaced element still answers with itself. + +textarea: +PASS: webArea.stringForTextMarkerRange(range) === "alpha\nbravo\n" +PASS: webArea.textMarkerRangeLength(range) === 12 + +text input: +PASS: webArea.stringForTextMarkerRange(range) === "alpha bravo" +PASS: webArea.textMarkerRangeLength(range) === 11 + +search input: +PASS: webArea.stringForTextMarkerRange(range) === "alpha bravo" +PASS: webArea.textMarkerRangeLength(range) === 11 + +contenteditable: +PASS: webArea.stringForTextMarkerRange(range) === "alpha\nbravo\n" +PASS: webArea.textMarkerRangeLength(range) === 12 + +image: +PASS: webArea.stringForTextMarkerRange(range) === "" +PASS: webArea.textMarkerRangeLength(range) === 1 + + +PASS successfullyParsed is true + +TEST COMPLETE + +alpha +bravo + diff --git a/LayoutTests/accessibility/mac/text-marker-range-for-text-control.html b/LayoutTests/accessibility/mac/text-marker-range-for-text-control.html new file mode 100644 index 000000000000..891a8b1b022d --- /dev/null +++ b/LayoutTests/accessibility/mac/text-marker-range-for-text-control.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + +
alpha
bravo
+A cake + + + + diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp index 2993967e00fb..9c32c1a1ab28 100644 --- a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp @@ -1107,15 +1107,6 @@ static bool NODELETE isFlowContent(Node& node) return text && !text->data().containsOnly(); } -bool AccessibilityNodeObject::isNativeTextControl() const -{ - if (is(node())) - return true; - - auto* input = dynamicDowncast(node()); - return input && (input->isText() || input->isNumberField()); -} - bool AccessibilityNodeObject::isSearchField() const { RefPtr node = this->node(); @@ -4135,9 +4126,9 @@ String AccessibilityNodeObject::text() const if (!isTextControl()) return { }; + if (RefPtr textControl = nativeTextControl()) + return textControl->value(); RefPtr element = dynamicDowncast(node()); - if (RefPtr formControl = dynamicDowncast(element); formControl && isNativeTextControl()) - return formControl->value(); return element ? element->innerText() : String(); } diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.h b/Source/WebCore/accessibility/AccessibilityNodeObject.h index d43f643c8b3e..1319864b2d88 100644 --- a/Source/WebCore/accessibility/AccessibilityNodeObject.h +++ b/Source/WebCore/accessibility/AccessibilityNodeObject.h @@ -65,7 +65,6 @@ class AccessibilityNodeObject : public AccessibilityObject { bool isDescriptionList() const final; bool isMultiSelectable() const override; bool NODELETE isNativeImage() const; - bool isNativeTextControl() const final; bool isSecureField() const final; bool isSearchField() const final; diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp index 628258691d90..273356db62ee 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -68,6 +68,7 @@ #include "FrameSelection.h" #include "GeometryUtilities.h" #include "HTMLAreaElement.h" +#include "HTMLBRElement.h" #include "HTMLBodyElement.h" #include "HTMLDataListElement.h" #include "HTMLDetailsElement.h" @@ -945,6 +946,38 @@ std::optional AccessibilityObject::simpleRange() const return AXObjectCache::rangeForNodeContents(*node); } +HTMLTextFormControlElement* AccessibilityObject::nativeTextControl() const +{ + if (auto* textArea = dynamicDowncast(node())) + return textArea; + + auto* input = dynamicDowncast(node()); + return input && (input->isText() || input->isNumberField()) ? input : nullptr; +} + +AXTextMarkerRange AccessibilityObject::textMarkerRange() const +{ + // A native text control's value lives in its shadow inner text element, so the host has no + // children whose contents to take: simpleRange covers the control as a single replaced object, + // which stringifies to an object replacement character rather than the value. + if (RefPtr textControl = nativeTextControl()) { + if (RefPtr innerText = textControl->innerTextElement()) { + auto range = AXObjectCache::rangeForNodeContents(*innerText); + // A value ending in a line break renders an empty final line, which + // HTMLTextFormControlElement::setInnerTextValue gives a line box by appending a + // placeholder
. That
's newline is collapsed out by rendering and is not a + // character of the value, so leave it out. + if (is(innerText->lastChild()) && range.end.offset) + --range.end.offset; + // A control with no value has no text to point at, so leave it pointing at itself, + // which is the only marker its callers can place in the document. + if (range.start != range.end) + return AXTextMarkerRange { std::optional { range } }; + } + } + return simpleRange(); +} + Vector AccessibilityObject::previousLineStartBoundaryPoints(const VisiblePosition& startingPosition, const SimpleRange& targetRange, unsigned positionsToRetrieve) const { Vector boundaryPoints; diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h index f8b1c21446b6..b74bf682977f 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.h +++ b/Source/WebCore/accessibility/AccessibilityObject.h @@ -64,6 +64,7 @@ WTF_ALLOW_COMPACT_POINTERS_TO_INCOMPLETE_TYPE(WebCore::AXObjectRareData); namespace WebCore { +class HTMLTextFormControlElement; class IntPoint; class IntSize; class ScrollableArea; @@ -140,7 +141,9 @@ class AccessibilityObject : public AXCoreObject { bool isSecureField() const override { return false; } bool isContainedBySecureField() const; - bool isNativeTextControl() const override { return false; } + bool isNativeTextControl() const final { return nativeTextControl(); } + // The + +
bystander
+ + + + diff --git a/LayoutTests/accessibility/mac/replace-range-at-block-boundary-expected.txt b/LayoutTests/accessibility/mac/replace-range-at-block-boundary-expected.txt new file mode 100644 index 000000000000..594a889cb7e9 --- /dev/null +++ b/LayoutTests/accessibility/mac/replace-range-at-block-boundary-expected.txt @@ -0,0 +1,100 @@ +Asserts that AXReplaceRangeWithText writes at the character index it is given, including the +index that starts a block, and clamps a range running past the end of the value to the end +rather than writing at the current selection. + +block ending in
, index starting the next block: value "alpha\nbravo\n\n", replacing {12, 0} +PASS: axField.replaceTextInRange("Y", 12, 0) === true +PASS: textOf(field) === "alpha\nbravo\nY\n" +PASS: textOf(bystander) === "bystander" + +block without a trailing
: value "alpha\nbravo\n\n", replacing {12, 0} +PASS: axField.replaceTextInRange("Y", 12, 0) === true +PASS: textOf(field) === "alpha\nbravo\nY\n" +PASS: textOf(bystander) === "bystander" + +
-separated lines: value "alpha\nbravo\n\n", replacing {12, 0} +PASS: axField.replaceTextInRange("Y", 12, 0) === true +PASS: textOf(field) === "alpha\nbravo\nY\n" +PASS: textOf(bystander) === "bystander" + +one line, index starting the blank second line: value "alpha\n\n", replacing {6, 0} +PASS: axField.replaceTextInRange("Y", 6, 0) === true +PASS: textOf(field) === "alpha\nY\n" +PASS: textOf(bystander) === "bystander" + +two blocks ending in
: value "alpha\nbravo\ncharlie\n\n", replacing {20, 0} +PASS: axField.replaceTextInRange("Y", 20, 0) === true +PASS: textOf(field) === "alpha\nbravo\ncharlie\nY\n" +PASS: textOf(bystander) === "bystander" + +interior index: value "alpha\nbravo\n\n", replacing {6, 0} +PASS: axField.replaceTextInRange("Y", 6, 0) === true +PASS: textOf(field) === "alpha\nYbravo\n\n" +PASS: textOf(bystander) === "bystander" + +replacing a run of characters: value "alpha\nbravo\n\n", replacing {6, 5} +PASS: axField.replaceTextInRange("Y", 6, 5) === true +PASS: textOf(field) === "alpha\nY\n\n" +PASS: textOf(bystander) === "bystander" + +field with no text: value "", replacing {0, 0} +PASS: axField.replaceTextInRange("Y", 0, 0) === true +PASS: textOf(field) === "Y" +PASS: textOf(bystander) === "bystander" + +textarea, index starting the blank final line: value "alpha\nbravo\n", replacing {12, 0} +PASS: axField.replaceTextInRange("Y", 12, 0) === true +PASS: textOf(field) === "alpha\nbravo\nY" +PASS: textOf(bystander) === "bystander" + +location one past the end of the value: value "alpha\nbravo\n\n", replacing {14, 0} +PASS: axField.replaceTextInRange("Y", 14, 0) === true +PASS: textOf(field) === "alpha\nbravo\nY\n" +PASS: textOf(bystander) === "bystander" + +location far past the end of the value: value "alpha\nbravo\n\n", replacing {99, 0} +PASS: axField.replaceTextInRange("Y", 99, 0) === true +PASS: textOf(field) === "alpha\nbravo\nY\n" +PASS: textOf(bystander) === "bystander" + +length running past the end of the value: value "alpha\nbravo\n\n", replacing {6, 99} +PASS: axField.replaceTextInRange("Y", 6, 99) === true +PASS: textOf(field) === "alpha\nY\n" +PASS: textOf(bystander) === "bystander" + + +PASS successfullyParsed is true + +TEST COMPLETE +alpha +bravo +Y +alpha +bravo +Y +alpha +bravo +Y +alpha +Y +alpha +bravo +charlie +Y +alpha +bravo +Y +alpha +bravo +Y +alpha +Y +alpha +Ybravo + +alpha +Y + +Y + +bystander diff --git a/LayoutTests/accessibility/mac/replace-range-at-block-boundary.html b/LayoutTests/accessibility/mac/replace-range-at-block-boundary.html new file mode 100644 index 000000000000..595a1fa8fae5 --- /dev/null +++ b/LayoutTests/accessibility/mac/replace-range-at-block-boundary.html @@ -0,0 +1,123 @@ + + + + + + + + + +
alpha
bravo

+ +
alpha
bravo

+
alpha
bravo

+ +
alpha

+ +
alpha
bravo
charlie

+ +
alpha
bravo

+
alpha
bravo

+
alpha
bravo

+ +
alpha
bravo

+
alpha
bravo

+ +
+ + + +
bystander
+ + + + diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp index 273356db62ee..6d31596a9f8d 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -2961,7 +2961,19 @@ bool AccessibilityObject::replaceTextInRange(const String& replacementString, co // Also only do this when the field is in editing mode. Ref frame = renderer()->frame(); if (element->shouldUseInputMethod()) { - frame->selection().setSelectedRange(rangeForCharacterRange(range), Affinity::Downstream, FrameSelection::ShouldCloseTyping::Yes); + uint64_t textLength = getLengthForTextRange(); + uint64_t startIndex = std::min(range.location, textLength); + uint64_t endIndex = startIndex + std::min(range.length, textLength - startIndex); + + auto start = visiblePositionForIndex(static_cast(startIndex)); + std::optional insertionRange = makeSimpleRange(start, endIndex == startIndex ? start : visiblePositionForIndex(static_cast(endIndex))); + if (!insertionRange) + return false; + + // Fail if the selection can't be set, otherwise the wrong text would be replaced. + if (!frame->selection().setSelectedRange(*insertionRange, Affinity::Downstream, FrameSelection::ShouldCloseTyping::Yes)) + return false; + protect(frame->editor())->replaceSelectionWithText(replacementString, Editor::SelectReplacement::No, Editor::SmartReplace::No); return true; } From 0d9f8faf3642c18971c354d7a3d756d116b5fa9b Mon Sep 17 00:00:00 2001 From: Chris Dumez Date: Thu, 27 Aug 2026 18:22:20 -0700 Subject: [PATCH 006/103] Drop RefCounted's adoption requirement assertion https://bugs.webkit.org/show_bug.cgi?id=322707 Reviewed by Ryosuke Niwa. The point of this assertion was to make sure that we do not forget to adopt a RefCounted object after constructing it. However, it also prevents ref'ing the object while it is in the middle of construction. Ref'ing the object while it is in the middle of construction is safer and something that happens routinely, especially when adopting our modern safer cpp programming patterns. This assertion was thus slowing down safer cpp adoption and we would keep addressing the crashes by adding calls to `relaxAdoptionRequirement()`, which disables the adoption requirement assertion for a specific object type. As a result, it makes little sense to keep this assertion around and I am thus removing it in this patch. * Source/WTF/wtf/NeverDestroyed.h: (WTF::NeverDestroyed::NeverDestroyed): (WTF::LazyNeverDestroyed::constructWithoutAccessCheck): (WTF::NeverDestroyed::MaybeRelax::MaybeRelax): Deleted. (): Deleted. (WTF::LazyNeverDestroyed::MaybeRelax::MaybeRelax): Deleted. * Source/WTF/wtf/Ref.h: (WTF::adoptRef): (WTF::adopted): Deleted. * Source/WTF/wtf/RefCountDebugger.h: (WTF::RefCountDebuggerImpl::~RefCountDebuggerImpl): (WTF::RefCountDebuggerImpl::willRef const): (WTF::RefCountDebuggerImpl::willDeref const): (WTF::RefCountDebuggerImpl::adopted): Deleted. (WTF::RefCountDebuggerImpl::relaxAdoptionRequirement): Deleted. * Source/WTF/wtf/RefCounted.h: (WTF::RefCountedBase::adopted): Deleted. (WTF::RefCountedBase::relaxAdoptionRequirement): Deleted. (WTF::adopted): Deleted. * Source/WTF/wtf/RefCountedWithInlineWeakPtr.h: (WTF::adopted): Deleted. * Source/WTF/wtf/RefPtr.h: (WTF::adoptRef): * Source/WTF/wtf/ThreadSafeRefCounted.h: (WTF::adopted): Deleted. * Source/WTF/wtf/ThreadSafeRefCountedWithSuppressingSaferCPPChecking.h: (WTF::adopted): Deleted. * Source/WTF/wtf/UniquelyOwnedPtr.h: (WTF::makeUniquelyOwned): * Source/WTF/wtf/glib/SocketConnection.cpp: (WTF::SocketConnection::SocketConnection): * Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp: (WebCore::MediaStreamTrack::MediaStreamTrack): * Source/WebCore/Modules/mediastream/RTCPeerConnection.cpp: (WebCore::RTCPeerConnection::RTCPeerConnection): * Source/WebCore/Modules/notifications/NotificationResourcesLoader.cpp: (WebCore::NotificationResourcesLoader::ResourceLoader::ResourceLoader): * Source/WebCore/accessibility/AccessibilityMenuList.cpp: (WebCore::AccessibilityMenuList::create): * Source/WebCore/accessibility/AccessibilitySpinButton.cpp: (WebCore::AccessibilitySpinButton::create): * Source/WebCore/dom/EmptyScriptExecutionContext.h: * Source/WebCore/dom/Node.cpp: (WebCore::Node::Node): (WebCore::Node::~Node): * Source/WebCore/dom/Node.h: (WebCore::Node::ref const): (WebCore::Node::deref const): (WebCore::adopted): Deleted. * Source/WebCore/dom/NodeInlines.h: (WebCore::Node::relaxAdoptionRequirement): Deleted. * Source/WebCore/dom/Subscriber.cpp: (WebCore::Subscriber::Subscriber): * Source/WebCore/dom/messageports/MessagePortChannel.cpp: (WebCore::m_registry): * Source/WebCore/fileapi/Blob.cpp: (WebCore::Blob::stream): * Source/WebCore/loader/DocumentThreadableLoader.cpp: (WebCore::DocumentThreadableLoader::DocumentThreadableLoader): * Source/WebCore/page/Frame.cpp: (WebCore::Frame::Frame): * Source/WebCore/page/Page.cpp: (WebCore::createMainFrame): * Source/WebCore/platform/graphics/Font.cpp: (WebCore::m_shouldNotBeUsedForArabic): * Source/WebCore/rendering/RenderScrollbar.cpp: (WebCore::RenderScrollbar::RenderScrollbar): * Source/WebCore/rendering/RenderWidget.cpp: (WebCore::RenderWidget::RenderWidget): * Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp: (WebCore::WorkerOrWorkletGlobalScope::WorkerOrWorkletGlobalScope): * Source/WebCore/workers/service/ServiceWorker.cpp: (WebCore::ServiceWorker::ServiceWorker): * Source/WebCore/workers/service/ServiceWorkerContainer.cpp: (WebCore::ServiceWorkerContainer::ensureSWClientConnection): * Source/WebCore/workers/service/ServiceWorkerRegistration.cpp: (WebCore::ServiceWorkerRegistration::ServiceWorkerRegistration): * Source/WebKit/NetworkProcess/Downloads/PendingDownload.cpp: (WebKit::PendingDownload::PendingDownload): * Source/WebKit/NetworkProcess/NetworkLoad.cpp: (WebKit::NetworkLoad::NetworkLoad): * Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp: (WebKit::NetworkSocketChannel::NetworkSocketChannel): * Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerNavigationPreloader.cpp: (WebKit::ServiceWorkerNavigationPreloader::ServiceWorkerNavigationPreloader): * Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerToContextConnection.cpp: (WebKit::WebSharedWorkerServerToContextConnection::WebSharedWorkerServerToContextConnection): * Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionMenuItemCocoa.mm: (WebKit::WebExtensionMenuItem::WebExtensionMenuItem): * Source/WebKit/UIProcess/ProvisionalPageProxy.cpp: (WebKit::ProvisionalPageProxy::ProvisionalPageProxy): * Source/WebKit/WebProcess/Databases/IndexedDB/WebIDBConnectionToServer.cpp: (WebKit::WebIDBConnectionToServer::WebIDBConnectionToServer): * Source/WebKit/webpushd/PushService.mm: (WebPushD::PushService::PushService): Canonical link: https://commits.webkit.org/320014@main --- Source/WTF/wtf/NeverDestroyed.h | 20 +++------------ Source/WTF/wtf/Ref.h | 3 --- Source/WTF/wtf/RefCountDebugger.h | 25 ------------------- Source/WTF/wtf/RefCounted.h | 9 ------- Source/WTF/wtf/RefCountedWithInlineWeakPtr.h | 10 -------- Source/WTF/wtf/RefPtr.h | 1 - Source/WTF/wtf/ThreadSafeRefCounted.h | 16 +----------- ...efCountedWithSuppressingSaferCPPChecking.h | 16 +----------- Source/WTF/wtf/UniquelyOwnedPtr.h | 1 - Source/WTF/wtf/glib/SocketConnection.cpp | 2 -- .../Modules/mediastream/MediaStreamTrack.cpp | 1 - .../Modules/mediastream/RTCPeerConnection.cpp | 1 - .../NotificationResourcesLoader.cpp | 2 -- .../accessibility/AccessibilityMenuList.cpp | 2 -- .../accessibility/AccessibilitySpinButton.cpp | 2 -- .../WebCore/dom/EmptyScriptExecutionContext.h | 1 - Source/WebCore/dom/Node.cpp | 7 +----- Source/WebCore/dom/Node.h | 18 ------------- Source/WebCore/dom/NodeInlines.h | 9 ------- Source/WebCore/dom/Subscriber.cpp | 1 - .../dom/messageports/MessagePortChannel.cpp | 2 -- Source/WebCore/fileapi/Blob.cpp | 1 - .../loader/DocumentThreadableLoader.cpp | 2 -- Source/WebCore/page/Frame.cpp | 1 - Source/WebCore/page/Page.cpp | 1 - Source/WebCore/platform/graphics/Font.cpp | 1 - Source/WebCore/rendering/RenderScrollbar.cpp | 3 --- Source/WebCore/rendering/RenderWidget.cpp | 1 - .../workers/WorkerOrWorkletGlobalScope.cpp | 1 - .../WebCore/workers/service/ServiceWorker.cpp | 1 - .../service/ServiceWorkerContainer.cpp | 1 - .../service/ServiceWorkerRegistration.cpp | 2 -- .../NetworkProcess/BackgroundFetchLoad.cpp | 1 - .../Downloads/PendingDownload.cpp | 2 -- Source/WebKit/NetworkProcess/NetworkLoad.cpp | 1 - .../NetworkProcess/NetworkSocketChannel.cpp | 1 - .../ServiceWorkerNavigationPreloader.cpp | 1 - .../ServiceWorkerSoftUpdateLoader.cpp | 1 - ...bSharedWorkerServerToContextConnection.cpp | 1 - .../NetworkCacheSpeculativeLoadManager.cpp | 1 - .../Cocoa/WebExtensionMenuItemCocoa.mm | 2 -- .../WebKit/UIProcess/ProvisionalPageProxy.cpp | 1 - .../IndexedDB/WebIDBConnectionToServer.cpp | 1 - Source/WebKit/webpushd/PushService.mm | 1 - 44 files changed, 6 insertions(+), 172 deletions(-) diff --git a/Source/WTF/wtf/NeverDestroyed.h b/Source/WTF/wtf/NeverDestroyed.h index 974eee494e3c..f6cbe8eb9fb2 100644 --- a/Source/WTF/wtf/NeverDestroyed.h +++ b/Source/WTF/wtf/NeverDestroyed.h @@ -72,13 +72,13 @@ template class NeverDestroyed { template NeverDestroyed(Args&&... args) { AccessTraits::assertAccess(); - MaybeRelax(new (storagePointer()) T(std::forward(args)...)); + new (storagePointer()) T(std::forward(args)...); } NeverDestroyed(NeverDestroyed&& other) { AccessTraits::assertAccess(); - MaybeRelax(new (storagePointer()) T(WTF::move(*other.storagePointer()))); + new (storagePointer()) T(WTF::move(*other.storagePointer())); } operator T&() { return *storagePointer(); } @@ -100,13 +100,6 @@ template class NeverDestroyed { return const_cast(m_storage.get()); } - template::value> struct MaybeRelax { - explicit MaybeRelax(PtrType*) { } - }; - template struct MaybeRelax { - explicit MaybeRelax(PtrType* ptr) { ptr->relaxAdoptionRequirement(); } - }; - // FIXME: Investigate whether we should allocate a hunk of virtual memory // and hand out chunks of it to NeverDestroyed instead, to reduce fragmentation. AlignedStorage m_storage; @@ -135,7 +128,7 @@ template class LazyNeverDestroyed { #if ASSERT_ENABLED m_isConstructed = true; #endif - MaybeRelax(new (storagePointerWithoutAccessCheck()) T(std::forward(args)...)); + new (storagePointerWithoutAccessCheck()) T(std::forward(args)...); } operator T&() { return *storagePointer(); } @@ -167,13 +160,6 @@ template class LazyNeverDestroyed { return storagePointerWithoutAccessCheck(); } - template::value> struct MaybeRelax { - explicit MaybeRelax(PtrType*) { } - }; - template struct MaybeRelax { - explicit MaybeRelax(PtrType* ptr) { ptr->relaxAdoptionRequirement(); } - }; - #if ASSERT_ENABLED // LazyNeverDestroyed objects are always static, so this variable is initialized to false. // It must not be initialized dynamically; that would not be thread safe. diff --git a/Source/WTF/wtf/Ref.h b/Source/WTF/wtf/Ref.h index 4af39c1cd381..3d2555e1e5c2 100644 --- a/Source/WTF/wtf/Ref.h +++ b/Source/WTF/wtf/Ref.h @@ -43,8 +43,6 @@ extern "C" int __asan_address_is_poisoned(void const volatile *addr); namespace WTF { -inline void adopted(const void*) { } - template struct DefaultRefDerefTraits { static constexpr bool isDefaultImplementation = true; @@ -353,7 +351,6 @@ struct IsSmartPtr> { template inline Ref adoptRef(T& reference) { - adopted(&reference); return Ref(reference, Ref::Adopt); } diff --git a/Source/WTF/wtf/RefCountDebugger.h b/Source/WTF/wtf/RefCountDebugger.h index 5361ef3086a3..7462deffbd8f 100644 --- a/Source/WTF/wtf/RefCountDebugger.h +++ b/Source/WTF/wtf/RefCountDebugger.h @@ -63,7 +63,6 @@ class RefCountDebuggerImpl : public RefCountDebuggerBase { ~RefCountDebuggerImpl() { ASSERT(m_deletionHasBegun); - ASSERT(!m_adoptionIsRequired); } #else ~RefCountDebuggerImpl() = default; @@ -73,25 +72,6 @@ class RefCountDebuggerImpl : public RefCountDebuggerBase { { applyRefDerefThreadingCheck(refCount); applyRefDuringDestructionCheck(); - -#if CHECK_REF_COUNTED_LIFECYCLE - ASSERT(!m_adoptionIsRequired); -#endif - } - - void adopted() - { -#if CHECK_REF_COUNTED_LIFECYCLE - m_adoptionIsRequired = false; -#endif - } - - void relaxAdoptionRequirement() - { -#if CHECK_REF_COUNTED_LIFECYCLE - ASSERT(m_adoptionIsRequired); - m_adoptionIsRequired = false; -#endif } // Unsafe precondition: The caller must ensure thread-safe access to this object, @@ -155,10 +135,6 @@ class RefCountDebuggerImpl : public RefCountDebuggerBase { { applyRefDerefThreadingCheck(refCount); -#if CHECK_REF_COUNTED_LIFECYCLE - ASSERT(!m_adoptionIsRequired); -#endif - ASSERT(refCount); } @@ -185,7 +161,6 @@ class RefCountDebuggerImpl : public RefCountDebuggerBase { #endif #if CHECK_REF_COUNTED_LIFECYCLE mutable std::atomic m_deletionHasBegun { false }; - mutable bool m_adoptionIsRequired { true }; #endif }; diff --git a/Source/WTF/wtf/RefCounted.h b/Source/WTF/wtf/RefCounted.h index 1633fa28e661..d5463a57c549 100644 --- a/Source/WTF/wtf/RefCounted.h +++ b/Source/WTF/wtf/RefCounted.h @@ -39,8 +39,6 @@ class RefCountedBase { uint32_t refCount() const { return m_refCount; } // Debug APIs - void adopted() { m_refCountDebugger.adopted(); } - void relaxAdoptionRequirement() { m_refCountDebugger.relaxAdoptionRequirement(); } void disableThreadingChecks() { m_refCountDebugger.disableThreadingChecks(); } RefCountDebugger& refCountDebugger() LIFETIME_BOUND { return m_refCountDebugger; } @@ -88,13 +86,6 @@ template class RefCounted : public RefCountedBase { ~RefCounted() = default; } SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT; -inline void adopted(RefCountedBase* object) -{ - if (!object) - return; - object->adopted(); -} - } // namespace WTF using WTF::RefCounted; diff --git a/Source/WTF/wtf/RefCountedWithInlineWeakPtr.h b/Source/WTF/wtf/RefCountedWithInlineWeakPtr.h index 8d14e42792d1..e35ff79d8886 100644 --- a/Source/WTF/wtf/RefCountedWithInlineWeakPtr.h +++ b/Source/WTF/wtf/RefCountedWithInlineWeakPtr.h @@ -193,16 +193,6 @@ template class RefCountedWithInlineWeakPtr { RefCountHeader& header() const { return refCountHeader(object()); } } SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT; -template - requires requires { typename U::RefCountedType; } -inline void adopted(U* object) -{ - if (!object) - return; - using T = typename U::RefCountedType; - refCountHeader(static_cast(object)).refCountDebugger().adopted(); -} - template Ref createRefCountedWithInlineWeakPtr(Args&&... args) { diff --git a/Source/WTF/wtf/RefPtr.h b/Source/WTF/wtf/RefPtr.h index 8523fbeab63c..33316d112f6d 100644 --- a/Source/WTF/wtf/RefPtr.h +++ b/Source/WTF/wtf/RefPtr.h @@ -265,7 +265,6 @@ inline bool operator==(const RefPtr& a, X* b) template inline RefPtr adoptRef(T* p) { - adopted(p); return RefPtr(p, RefPtr::Adopt); } diff --git a/Source/WTF/wtf/ThreadSafeRefCounted.h b/Source/WTF/wtf/ThreadSafeRefCounted.h index 71e7c4573e07..9426a1b4ea1a 100644 --- a/Source/WTF/wtf/ThreadSafeRefCounted.h +++ b/Source/WTF/wtf/ThreadSafeRefCounted.h @@ -48,18 +48,11 @@ class WTF_EMPTY_BASE_CLASS ThreadSafeRefCountedBase { uint32_t refCount() const { return m_refCount.load(std::memory_order_relaxed); } // Debug APIs - void adopted() { m_refCountDebugger.adopted(); } - void relaxAdoptionRequirement() { m_refCountDebugger.relaxAdoptionRequirement(); } void disableThreadingChecks() { m_refCountDebugger.disableThreadingChecks(); } ThreadSafeRefCountDebugger& refCountDebugger() LIFETIME_BOUND { return m_refCountDebugger; } protected: - ThreadSafeRefCountedBase() - { - // FIXME: Lots of subclasses violate our adoption requirements. Migrate - // this call into only those subclasses that need it. - m_refCountDebugger.relaxAdoptionRequirement(); - } + ThreadSafeRefCountedBase() = default; ~ThreadSafeRefCountedBase() { @@ -114,13 +107,6 @@ template ~ThreadSafeRefCounted() = default; } SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT; -inline void adopted(ThreadSafeRefCountedBase* object) -{ - if (!object) - return; - object->adopted(); -} - } // namespace WTF using WTF::ThreadSafeRefCounted; diff --git a/Source/WTF/wtf/ThreadSafeRefCountedWithSuppressingSaferCPPChecking.h b/Source/WTF/wtf/ThreadSafeRefCountedWithSuppressingSaferCPPChecking.h index e8a34cc5381f..95fd440d778f 100644 --- a/Source/WTF/wtf/ThreadSafeRefCountedWithSuppressingSaferCPPChecking.h +++ b/Source/WTF/wtf/ThreadSafeRefCountedWithSuppressingSaferCPPChecking.h @@ -50,18 +50,11 @@ class WTF_EMPTY_BASE_CLASS ThreadSafeRefCountedWithSuppressingSaferCPPCheckingBa uint32_t refCount() const { return m_refCount.load(std::memory_order_relaxed); } // Debug APIs - void adopted() { m_refCountDebugger.adopted(); } - void relaxAdoptionRequirement() { m_refCountDebugger.relaxAdoptionRequirement(); } void disableThreadingChecks() { m_refCountDebugger.disableThreadingChecks(); } ThreadSafeRefCountDebugger& refCountDebugger() LIFETIME_BOUND { return m_refCountDebugger; } protected: - ThreadSafeRefCountedWithSuppressingSaferCPPCheckingBase() - { - // FIXME: Lots of subclasses violate our adoption requirements. Migrate - // this call into only those subclasses that need it. - m_refCountDebugger.relaxAdoptionRequirement(); - } + ThreadSafeRefCountedWithSuppressingSaferCPPCheckingBase() = default; ~ThreadSafeRefCountedWithSuppressingSaferCPPCheckingBase() { @@ -114,13 +107,6 @@ template } } SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT; -inline void adopted(ThreadSafeRefCountedWithSuppressingSaferCPPCheckingBase* object) -{ - if (!object) - return; - object->adopted(); -} - } // namespace WTF using WTF::ThreadSafeRefCountedWithSuppressingSaferCPPChecking; diff --git a/Source/WTF/wtf/UniquelyOwnedPtr.h b/Source/WTF/wtf/UniquelyOwnedPtr.h index b4fcf6f82300..d224ffecd12f 100644 --- a/Source/WTF/wtf/UniquelyOwnedPtr.h +++ b/Source/WTF/wtf/UniquelyOwnedPtr.h @@ -43,7 +43,6 @@ UniquelyOwnedPtr makeUniquelyOwned(Args&&... args) { using T = typename U::RefCountedType; auto* object = RefCountedWithInlineWeakPtr::template create(std::forward(args)...); - adopted(object); return UniquelyOwnedPtr(object); } diff --git a/Source/WTF/wtf/glib/SocketConnection.cpp b/Source/WTF/wtf/glib/SocketConnection.cpp index dc3e7091b8d2..345b6668096c 100644 --- a/Source/WTF/wtf/glib/SocketConnection.cpp +++ b/Source/WTF/wtf/glib/SocketConnection.cpp @@ -39,8 +39,6 @@ SocketConnection::SocketConnection(GRefPtr&& connection, cons , m_messageHandlers(messageHandlers) , m_userData(userData) { - relaxAdoptionRequirement(); - m_readBuffer.reserveInitialCapacity(defaultBufferSize); m_writeBuffer.reserveInitialCapacity(defaultBufferSize); diff --git a/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp b/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp index f91bb78204d5..cd9a3d954e42 100644 --- a/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp +++ b/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp @@ -93,7 +93,6 @@ MediaStreamTrack::MediaStreamTrack(ScriptExecutionContext& context, Refmuted()) , m_isCaptureTrack(is(context) && m_private->isCaptureTrack()) { - relaxAdoptionRequirement(); ALWAYS_LOG(LOGIDENTIFIER); m_private->addObserver(*this); diff --git a/Source/WebCore/Modules/mediastream/RTCPeerConnection.cpp b/Source/WebCore/Modules/mediastream/RTCPeerConnection.cpp index 0d1b18518e85..aba3bb1ea99f 100644 --- a/Source/WebCore/Modules/mediastream/RTCPeerConnection.cpp +++ b/Source/WebCore/Modules/mediastream/RTCPeerConnection.cpp @@ -130,7 +130,6 @@ RTCPeerConnection::RTCPeerConnection(Document& document) #endif { ALWAYS_LOG(LOGIDENTIFIER); - relaxAdoptionRequirement(); } RTCPeerConnection::~RTCPeerConnection() diff --git a/Source/WebCore/Modules/notifications/NotificationResourcesLoader.cpp b/Source/WebCore/Modules/notifications/NotificationResourcesLoader.cpp index c56a75a99c5d..1d02de7ffe61 100644 --- a/Source/WebCore/Modules/notifications/NotificationResourcesLoader.cpp +++ b/Source/WebCore/Modules/notifications/NotificationResourcesLoader.cpp @@ -139,8 +139,6 @@ auto NotificationResourcesLoader::ResourceLoader::create(ScriptExecutionContext& NotificationResourcesLoader::ResourceLoader::ResourceLoader(ScriptExecutionContext& context, const URL& url, CompletionHandler&&)>&& completionHandler) : m_completionHandler(WTF::move(completionHandler)) { - relaxAdoptionRequirement(); - ThreadableLoaderOptions options; options.mode = FetchOptions::Mode::Cors; options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks; diff --git a/Source/WebCore/accessibility/AccessibilityMenuList.cpp b/Source/WebCore/accessibility/AccessibilityMenuList.cpp index 82058fa189df..7de9e2acacab 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuList.cpp +++ b/Source/WebCore/accessibility/AccessibilityMenuList.cpp @@ -48,8 +48,6 @@ AccessibilityMenuList::AccessibilityMenuList(AXID axID, RenderObject& renderer, Ref AccessibilityMenuList::create(AXID axID, RenderObject& renderer, AXObjectCache& cache) { Ref menuList = adoptRef(*new AccessibilityMenuList(axID, renderer, cache)); - // We have to do this setup here and not in the constructor to avoid an - // adoptionIsRequired ASSERT in RefCounted.h. menuList->m_popup->setParent(menuList.ptr()); menuList->addChild(menuList->m_popup.get()); menuList->m_childrenInitialized = true; diff --git a/Source/WebCore/accessibility/AccessibilitySpinButton.cpp b/Source/WebCore/accessibility/AccessibilitySpinButton.cpp index 7caa76f9891e..7890b61246ff 100644 --- a/Source/WebCore/accessibility/AccessibilitySpinButton.cpp +++ b/Source/WebCore/accessibility/AccessibilitySpinButton.cpp @@ -53,8 +53,6 @@ AccessibilitySpinButton::AccessibilitySpinButton(AXID axID, SpinButtonElement& s Ref AccessibilitySpinButton::create(AXID axID, SpinButtonElement& spinButtonElement, AXObjectCache& cache) { Ref spinButton = adoptRef(*new AccessibilitySpinButton(axID, spinButtonElement, cache)); - // We have to do this setup here and not in the constructor to avoid an - // adoptionIsRequired ASSERT in RefCounted.h. spinButton->m_incrementor->setParent(spinButton.ptr()); spinButton->m_decrementor->setParent(spinButton.ptr()); spinButton->addChild(spinButton->m_incrementor.get()); diff --git a/Source/WebCore/dom/EmptyScriptExecutionContext.h b/Source/WebCore/dom/EmptyScriptExecutionContext.h index 56d6bc207a46..5eb26ae28086 100644 --- a/Source/WebCore/dom/EmptyScriptExecutionContext.h +++ b/Source/WebCore/dom/EmptyScriptExecutionContext.h @@ -100,7 +100,6 @@ class EmptyScriptExecutionContext final : public RefCounted(m_eventLoop)) { - relaxAdoptionRequirement(); m_eventLoop->addAssociatedContext(*this); } diff --git a/Source/WebCore/dom/Node.cpp b/Source/WebCore/dom/Node.cpp index e8e88ad88e2d..359a3bd81f9a 100644 --- a/Source/WebCore/dom/Node.cpp +++ b/Source/WebCore/dom/Node.cpp @@ -123,7 +123,6 @@ struct SameSizeAsNode : EventTarget, CanMakeCheckedPtr { public: #if ASSERT_ENABLED bool inRemovedLastRefFunction; - bool adoptionIsRequired; bool deletionHasBegun; #endif uint32_t refCountAndParentBit; @@ -385,10 +384,7 @@ Node::Node(Document& document, NodeType type, OptionSet flags) ASSERT(nodeType() == type); ASSERT(isMainThread()); - // Allow code to ref the Document while it is being constructed to make our life easier. - if (isDocumentNode()) - relaxAdoptionRequirement(); - else + if (!isDocumentNode()) document.incrementReferencingNodeCount(); #if !defined(NDEBUG) || DUMP_NODE_STATISTICS @@ -411,7 +407,6 @@ Node::~Node() { ASSERT(isMainThread()); ASSERT(deletionHasBegun()); - ASSERT(!m_adoptionIsRequired); InspectorInstrumentation::willDestroyDOMNode(*this); diff --git a/Source/WebCore/dom/Node.h b/Source/WebCore/dom/Node.h index 766e6cff4522..cbf18175cc8b 100644 --- a/Source/WebCore/dom/Node.h +++ b/Source/WebCore/dom/Node.h @@ -572,8 +572,6 @@ class Node : public EventTarget, public CanMakeCheckedPtr { ALWAYS_INLINE unsigned refCount() const; void applyRefDuringDestructionCheck() const; - inline void relaxAdoptionRequirement(); - HashMap, MutationRecordDeliveryOptions> registeredMutationObservers(MutationObserverOptionType, const QualifiedName* attributeName); void registerMutationObserver(MutationObserver&, MutationObserverOptions, const MemoryCompactLookupOnlyRobinHoodHashSet& attributeFilter); void unregisterMutationObserver(MutationObserverRegistration&); @@ -812,10 +810,7 @@ class Node : public EventTarget, public CanMakeCheckedPtr { #if ASSERT_ENABLED mutable bool m_inRemovedLastRefFunction { false }; - bool m_adoptionIsRequired { true }; bool m_deletionHasBegun { false }; - - friend inline void adopted(Node*); #endif mutable uint32_t m_refCountAndParentBit { s_refCountIncrement }; @@ -843,21 +838,9 @@ WEBCORE_EXPORT std::partial_ordering treeOrderForTesting(TreeType, const Node&, bool NODELETE isTouchRelatedEventType(const EventTypeInfo&, const EventTarget&); -#if ASSERT_ENABLED - -inline void adopted(Node* node) -{ - if (!node) - return; - node->m_adoptionIsRequired = false; -} - -#endif // ASSERT_ENABLED - ALWAYS_INLINE void Node::ref() const { ASSERT(isMainThread()); - ASSERT(!m_adoptionIsRequired); applyRefDuringDestructionCheck(); m_refCountAndParentBit += s_refCountIncrement; } @@ -874,7 +857,6 @@ inline void Node::applyRefDuringDestructionCheck() const ALWAYS_INLINE void Node::deref() const { ASSERT(isMainThread()); - ASSERT(!m_adoptionIsRequired); ASSERT_WITH_SECURITY_IMPLICATION(refCount()); auto updatedRefCount = m_refCountAndParentBit - s_refCountIncrement; diff --git a/Source/WebCore/dom/NodeInlines.h b/Source/WebCore/dom/NodeInlines.h index 9f203322490c..74e88bbf836e 100644 --- a/Source/WebCore/dom/NodeInlines.h +++ b/Source/WebCore/dom/NodeInlines.h @@ -238,15 +238,6 @@ inline NodeClass& Node::traverseToRootNodeInternal(const NodeClass& node) return *current; } -inline void Node::relaxAdoptionRequirement() -{ -#if ASSERT_ENABLED - ASSERT_WITH_SECURITY_IMPLICATION(!deletionHasBegun()); - ASSERT(m_adoptionIsRequired); - m_adoptionIsRequired = false; -#endif -} - inline IntRect Node::pixelSnappedAbsoluteBoundingRect(bool* isReplaced) { return snappedIntRect(absoluteBoundingRect(isReplaced)); diff --git a/Source/WebCore/dom/Subscriber.cpp b/Source/WebCore/dom/Subscriber.cpp index 1fce0c37b3ea..90221898f43a 100644 --- a/Source/WebCore/dom/Subscriber.cpp +++ b/Source/WebCore/dom/Subscriber.cpp @@ -51,7 +51,6 @@ Subscriber::Subscriber(ScriptExecutionContext& context, Ref&& , m_options(options) { m_observer->setSubscriber(*this); - relaxAdoptionRequirement(); followSignal(m_signal); if (RefPtr signal = options.signal) followSignal(*signal); diff --git a/Source/WebCore/dom/messageports/MessagePortChannel.cpp b/Source/WebCore/dom/messageports/MessagePortChannel.cpp index 5e680fd5ebfb..e94147222e03 100644 --- a/Source/WebCore/dom/messageports/MessagePortChannel.cpp +++ b/Source/WebCore/dom/messageports/MessagePortChannel.cpp @@ -44,8 +44,6 @@ MessagePortChannel::MessagePortChannel(MessagePortChannelRegistry& registry, con { ASSERT(isMainThread()); - relaxAdoptionRequirement(); - m_processes[0] = port1.processIdentifier; m_entangledToProcessProtectors[0] = this; m_processes[1] = port2.processIdentifier; diff --git a/Source/WebCore/fileapi/Blob.cpp b/Source/WebCore/fileapi/Blob.cpp index bd00721caf2c..6831443ec217 100644 --- a/Source/WebCore/fileapi/Blob.cpp +++ b/Source/WebCore/fileapi/Blob.cpp @@ -397,7 +397,6 @@ ExceptionOr> Blob::stream() BlobStreamSource(ScriptExecutionContext& scriptExecutionContext, Blob& blob) : m_loader(FileReaderLoader::create(FileReaderLoader::ReadType::ReadAsBinaryChunks, this)) { - relaxAdoptionRequirement(); m_loader->start(&scriptExecutionContext, blob); } diff --git a/Source/WebCore/loader/DocumentThreadableLoader.cpp b/Source/WebCore/loader/DocumentThreadableLoader.cpp index 90e9f506335b..f675afeed7ae 100644 --- a/Source/WebCore/loader/DocumentThreadableLoader.cpp +++ b/Source/WebCore/loader/DocumentThreadableLoader.cpp @@ -132,8 +132,6 @@ DocumentThreadableLoader::DocumentThreadableLoader(Document& document, Threadabl , m_crossOriginEmbedderPolicy(WTF::move(crossOriginEmbedderPolicy)) , m_shouldLogError(shouldLogError) { - relaxAdoptionRequirement(); - // Setting a referrer header is only supported in the async code path. ASSERT(m_async || m_referrer.isEmpty()); diff --git a/Source/WebCore/page/Frame.cpp b/Source/WebCore/page/Frame.cpp index 35fce1e5c23b..cdf8f3bf742a 100644 --- a/Source/WebCore/page/Frame.cpp +++ b/Source/WebCore/page/Frame.cpp @@ -126,7 +126,6 @@ Frame::Frame(Page& page, FrameIdentifier frameID, FrameType frameType, HTMLFrame , m_opener(opener) , m_frameTreeSyncData(WTF::move(frameTreeSyncData)) { - relaxAdoptionRequirement(); if (parent && addToFrameTree == AddToFrameTree::Yes) parent->tree().appendChild(*this); diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp index 791e1ee2d55e..0e0f0ccc802a 100644 --- a/Source/WebCore/page/Page.cpp +++ b/Source/WebCore/page/Page.cpp @@ -367,7 +367,6 @@ static constexpr OptionSet pageInitialActivityState() GCC_MAYBE_NO_INLINE static Ref createMainFrame(Page& page, PageConfiguration::MainFrameCreationParameters&& clientCreator, RefPtr mainFrameOpener, FrameIdentifier identifier, Ref&& frameTreeSyncData) { - page.relaxAdoptionRequirement(); return switchOn(WTF::move(clientCreator), [&] (PageConfiguration::LocalMainFrameCreationParameters&& creationParameters) -> Ref { return LocalFrame::createMainFrame(page, WTF::move(creationParameters.clientCreator), identifier, creationParameters.effectiveSandboxFlags, creationParameters.effectiveReferrerPolicy, mainFrameOpener.get(), WTF::move(frameTreeSyncData)); }, [&] (CompletionHandler(RemoteFrame&)>&& remoteFrameClientCreator) -> Ref { diff --git a/Source/WebCore/platform/graphics/Font.cpp b/Source/WebCore/platform/graphics/Font.cpp index f7fb568a4b79..0fb72460c425 100644 --- a/Source/WebCore/platform/graphics/Font.cpp +++ b/Source/WebCore/platform/graphics/Font.cpp @@ -105,7 +105,6 @@ Font::Font(const FontPlatformData& platformData, Origin origin, IsInterstitial i , m_shouldNotBeUsedForArabic(false) #endif { - relaxAdoptionRequirement(); platformInit(); platformGlyphInit(); platformCharWidthInit(); diff --git a/Source/WebCore/rendering/RenderScrollbar.cpp b/Source/WebCore/rendering/RenderScrollbar.cpp index 8decf38b38b9..a109445c4197 100644 --- a/Source/WebCore/rendering/RenderScrollbar.cpp +++ b/Source/WebCore/rendering/RenderScrollbar.cpp @@ -55,9 +55,6 @@ RenderScrollbar::RenderScrollbar(ScrollableArea& scrollableArea, ScrollbarOrient { ASSERT(ownerElement || owningFrame); - // FIXME: We need to do this because RenderScrollbar::styleChanged is called as soon as the scrollbar is created. - relaxAdoptionRequirement(); - // Update the scrollbar size. int width = 0; int height = 0; diff --git a/Source/WebCore/rendering/RenderWidget.cpp b/Source/WebCore/rendering/RenderWidget.cpp index f15823b09dae..b5018fa1e410 100644 --- a/Source/WebCore/rendering/RenderWidget.cpp +++ b/Source/WebCore/rendering/RenderWidget.cpp @@ -103,7 +103,6 @@ static void moveWidgetToParentSoon(Widget& child, LocalFrameView* parent) RenderWidget::RenderWidget(Type type, HTMLFrameOwnerElement& element, Style::ComputedStyle&& style) : RenderReplaced(type, element, WTF::move(style), ReplacedFlag::IsWidget) { - relaxAdoptionRequirement(); setInline(false); } diff --git a/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp b/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp index 853c36462f53..21bfcd647e70 100644 --- a/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp +++ b/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp @@ -56,7 +56,6 @@ WorkerOrWorkletGlobalScope::WorkerOrWorkletGlobalScope(WorkerThreadType type, PA , m_noiseInjectionHashSalt(noiseInjectionHashSalt) , m_advancedPrivacyProtections(advancedPrivacyProtections) { - relaxAdoptionRequirement(); } WorkerOrWorkletGlobalScope::~WorkerOrWorkletGlobalScope() = default; diff --git a/Source/WebCore/workers/service/ServiceWorker.cpp b/Source/WebCore/workers/service/ServiceWorker.cpp index 88f7ba134117..174b2a36626d 100644 --- a/Source/WebCore/workers/service/ServiceWorker.cpp +++ b/Source/WebCore/workers/service/ServiceWorker.cpp @@ -71,7 +71,6 @@ ServiceWorker::ServiceWorker(ScriptExecutionContext& context, ServiceWorkerData& { context.registerServiceWorker(*this); - relaxAdoptionRequirement(); updatePendingActivityForEventDispatch(); WORKER_RELEASE_LOG("serviceWorkerID=%" PRIu64 ", state=%hhu", identifier().toUInt64(), std::to_underlying(m_data.state)); diff --git a/Source/WebCore/workers/service/ServiceWorkerContainer.cpp b/Source/WebCore/workers/service/ServiceWorkerContainer.cpp index ba18df2c7bc7..ecc335bd8de5 100644 --- a/Source/WebCore/workers/service/ServiceWorkerContainer.cpp +++ b/Source/WebCore/workers/service/ServiceWorkerContainer.cpp @@ -609,7 +609,6 @@ SWClientConnection& ServiceWorkerContainer::ensureSWClientConnection() { ASSERT(scriptExecutionContext()); if (!m_swConnection || m_swConnection->isClosed()) { - // Using RefPtr here results in an m_adoptionIsRequired assert. if (RefPtr workerGlobal = dynamicDowncast(*scriptExecutionContext())) m_swConnection = workerGlobal->swClientConnection(); else diff --git a/Source/WebCore/workers/service/ServiceWorkerRegistration.cpp b/Source/WebCore/workers/service/ServiceWorkerRegistration.cpp index 8a60436e4bd1..cffcb43be225 100644 --- a/Source/WebCore/workers/service/ServiceWorkerRegistration.cpp +++ b/Source/WebCore/workers/service/ServiceWorkerRegistration.cpp @@ -90,8 +90,6 @@ ServiceWorkerRegistration::ServiceWorkerRegistration(ScriptExecutionContext& con REGISTRATION_RELEASE_LOG("ServiceWorkerRegistration: ID %" PRIu64 ", installing=%" PRIu64 ", waiting=%" PRIu64 ", active=%" PRIu64, identifier().toUInt64(), m_installingWorker ? m_installingWorker->identifier().toUInt64() : 0, m_waitingWorker ? m_waitingWorker->identifier().toUInt64() : 0, m_activeWorker ? m_activeWorker->identifier().toUInt64() : 0); m_container->addRegistration(*this); - - relaxAdoptionRequirement(); } ServiceWorkerRegistration::~ServiceWorkerRegistration() diff --git a/Source/WebKit/NetworkProcess/BackgroundFetchLoad.cpp b/Source/WebKit/NetworkProcess/BackgroundFetchLoad.cpp index 80292dfdb296..0252930a5655 100644 --- a/Source/WebKit/NetworkProcess/BackgroundFetchLoad.cpp +++ b/Source/WebKit/NetworkProcess/BackgroundFetchLoad.cpp @@ -52,7 +52,6 @@ BackgroundFetchLoad::BackgroundFetchLoad(NetworkProcess& networkProcess, PAL::Se , m_request(request.internalRequest) , m_networkLoadChecker(NetworkLoadChecker::create(networkProcess, nullptr, nullptr, FetchOptions { request.options }, m_sessionID, std::nullopt, HTTPHeaderMap { request.httpHeaders }, URL { m_request.url() }, URL { }, clientOrigin.clientOrigin.securityOrigin(), clientOrigin.topOrigin.securityOrigin(), RefPtr { }, PreflightPolicy::Consider, String { request.referrer }, true, OptionSet { })) { - relaxAdoptionRequirement(); if (!m_request.url().protocolIsInHTTPFamily()) { didFinish(ResourceError { String { }, 0, m_request.url(), "URL is not HTTP(S)"_s, ResourceError::Type::Cancellation }); return; diff --git a/Source/WebKit/NetworkProcess/Downloads/PendingDownload.cpp b/Source/WebKit/NetworkProcess/Downloads/PendingDownload.cpp index 6367e89b0156..4bb56b60d9d2 100644 --- a/Source/WebKit/NetworkProcess/Downloads/PendingDownload.cpp +++ b/Source/WebKit/NetworkProcess/Downloads/PendingDownload.cpp @@ -53,8 +53,6 @@ PendingDownload::PendingDownload(IPC::Connection* parentProcessConnection, Netwo , m_fromDownloadAttribute(fromDownloadAttribute) , m_webProcessID(webProcessID) { - relaxAdoptionRequirement(); - #if ENABLE(CONTENT_FILTERING) #if HAVE(BROWSERENGINEKIT_WEBCONTENTFILTER) && !HAVE(WEBCONTENTRESTRICTIONS_PATH_SPI) WebParentalControlsURLFilter::setSharedParentalControlsURLFilterIfNecessary(); diff --git a/Source/WebKit/NetworkProcess/NetworkLoad.cpp b/Source/WebKit/NetworkProcess/NetworkLoad.cpp index 8e162807dd86..b21c28f93f52 100644 --- a/Source/WebKit/NetworkProcess/NetworkLoad.cpp +++ b/Source/WebKit/NetworkProcess/NetworkLoad.cpp @@ -56,7 +56,6 @@ NetworkLoad::NetworkLoad(NetworkLoadClient& client, NetworkLoadParameters&& para , m_parameters(WTF::move(parameters)) , m_currentRequest(m_parameters.request) { - relaxAdoptionRequirement(); if (m_parameters.request.url().protocolIsBlob()) m_task = NetworkDataTaskBlob::create(networkSession, *this, m_parameters.request, m_parameters.blobFileReferences, m_parameters.topOrigin); else diff --git a/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp b/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp index 8b62f5edfcbc..2f7ef5bda164 100644 --- a/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp +++ b/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp @@ -60,7 +60,6 @@ NetworkSocketChannel::NetworkSocketChannel(NetworkConnectionToWebProcess& connec , m_errorTimer(*this, &NetworkSocketChannel::sendDelayedError) , m_webPageProxyID(webPageProxyID) { - relaxAdoptionRequirement(); if (!session) return; diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerNavigationPreloader.cpp b/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerNavigationPreloader.cpp index 4bb01a98b2ab..c9e8bb89f1a2 100644 --- a/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerNavigationPreloader.cpp +++ b/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerNavigationPreloader.cpp @@ -54,7 +54,6 @@ ServiceWorkerNavigationPreloader::ServiceWorkerNavigationPreloader(NetworkSessio , m_shouldCaptureExtraNetworkLoadMetrics(shouldCaptureExtraNetworkLoadMetrics) , m_startTime(MonotonicTime::now()) { - relaxAdoptionRequirement(); RELEASE_LOG(ServiceWorker, "ServiceWorkerNavigationPreloader::ServiceWorkerNavigationPreloader %p", this); start(); } diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerSoftUpdateLoader.cpp b/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerSoftUpdateLoader.cpp index f6631cb13e7e..bb712efc6a26 100644 --- a/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerSoftUpdateLoader.cpp +++ b/Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerSoftUpdateLoader.cpp @@ -54,7 +54,6 @@ ServiceWorkerSoftUpdateLoader::ServiceWorkerSoftUpdateLoader(NetworkSession& ses , m_jobData(WTF::move(jobData)) , m_session(session) { - relaxAdoptionRequirement(); ASSERT(!request.isConditional()); if (RefPtr cache = session.cache()) { diff --git a/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerToContextConnection.cpp b/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerToContextConnection.cpp index a5b8d6c3cfb5..68c5d14b3a73 100644 --- a/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerToContextConnection.cpp +++ b/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerToContextConnection.cpp @@ -65,7 +65,6 @@ WebSharedWorkerServerToContextConnection::WebSharedWorkerServerToContextConnecti , m_crossOriginEmbedderPolicyValue(crossOriginEmbedderPolicy) { CONTEXT_CONNECTION_RELEASE_LOG("WebSharedWorkerServerToContextConnection:"); - relaxAdoptionRequirement(); server.addContextConnection(*this); } diff --git a/Source/WebKit/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp b/Source/WebKit/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp index 5ec9f094f620..43b1d43ceb58 100644 --- a/Source/WebKit/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp +++ b/Source/WebKit/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp @@ -231,7 +231,6 @@ class SpeculativeLoadManager::PendingFrameLoad : public RefCountedAndCanMakeWeak protectedThis->markLoadAsCompleted(); }) { - relaxAdoptionRequirement(); m_loadHysteresisActivity.impulse(); } diff --git a/Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionMenuItemCocoa.mm b/Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionMenuItemCocoa.mm index 0f06bf34d011..bb8e8bb840c3 100644 --- a/Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionMenuItemCocoa.mm +++ b/Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionMenuItemCocoa.mm @@ -100,8 +100,6 @@ + (BOOL)usesUserKeyEquivalents , m_enabled(parameters.enabled.value_or(true)) , m_visible(parameters.visible.value_or(true)) { - relaxAdoptionRequirement(); - if (parameters.parentIdentifier) { if (RefPtr parentMenuItem = extensionContext.menuItem(parameters.parentIdentifier.value())) parentMenuItem->addSubmenuItem(*this); diff --git a/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp b/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp index 0c7d5b24a3f1..736047a05f53 100644 --- a/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp +++ b/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp @@ -106,7 +106,6 @@ ProvisionalPageProxy::ProvisionalPageProxy(WebPageProxy& page, Ref #endif #endif { - relaxAdoptionRequirement(); PROVISIONALPAGEPROXY_RELEASE_LOG(ProcessSwapping, "ProvisionalPageProxy: suspendedPage=%p", suspendedPage.get()); Ref process = this->process(); diff --git a/Source/WebKit/WebProcess/Databases/IndexedDB/WebIDBConnectionToServer.cpp b/Source/WebKit/WebProcess/Databases/IndexedDB/WebIDBConnectionToServer.cpp index 61ca09d3db8f..a5828dbc007b 100644 --- a/Source/WebKit/WebProcess/Databases/IndexedDB/WebIDBConnectionToServer.cpp +++ b/Source/WebKit/WebProcess/Databases/IndexedDB/WebIDBConnectionToServer.cpp @@ -59,7 +59,6 @@ Ref WebIDBConnectionToServer::create(PAL::SessionID se WebIDBConnectionToServer::WebIDBConnectionToServer(PAL::SessionID sessionID) { - relaxAdoptionRequirement(); lazyInitialize(m_connectionToServer, IDBClient::IDBConnectionToServer::create(*this, sessionID)); } diff --git a/Source/WebKit/webpushd/PushService.mm b/Source/WebKit/webpushd/PushService.mm index 43b628ab1450..66b4f3d6e5fc 100644 --- a/Source/WebKit/webpushd/PushService.mm +++ b/Source/WebKit/webpushd/PushService.mm @@ -183,7 +183,6 @@ static void performAfterFirstUnlock(Function&& function) , m_incomingPushMessageHandler(WTF::move(incomingPushMessageHandler)) { RELEASE_ASSERT(m_incomingPushMessageHandler); - relaxAdoptionRequirement(); Ref connection = m_connection; connection->startListeningForPublicToken([weakThis = WeakPtr { *this }](auto&& token) mutable { From 4ae53427d5d8e52791da2cfe0c0c98b877b2b5d5 Mon Sep 17 00:00:00 2001 From: Brian Weinstein Date: Thu, 27 Aug 2026 19:34:39 -0700 Subject: [PATCH 007/103] Fix compilation issue with WK_WEB_EXTENSIONS_OFFSCREEN on iOS https://bugs.webkit.org/show_bug.cgi?id=322782 rdar://184269255 Reviewed by Timothy Hatcher. -[UIWindow initWithFrame] is deprecated in iOS 26. Use -initWithWindowScene: instead, and have the class choose a foreground scene to use. * Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm: (WebKit::windowScene): (WebKit::WebExtensionContext::offscreenCreateDocument): Canonical link: https://commits.webkit.org/320015@main --- .../API/WebExtensionContextAPIOffscreenCocoa.mm | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm index b8ec9d3c6cca..9aab3249ac33 100644 --- a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm +++ b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm @@ -49,6 +49,17 @@ return false; } +#if PLATFORM(IOS_FAMILY) +static UIWindowScene *windowScene() +{ + for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { + if ([scene isKindOfClass:UIWindowScene.class] && (scene.activationState == UISceneActivationStateForegroundActive || scene.activationState == UISceneActivationStateForegroundInactive)) + return (UIWindowScene *)scene; + } + return nil; +} +#endif + void WebExtensionContext::offscreenCreateDocument(const WebExtensionOffscreenDocumentParameters& parameters, CompletionHandler&&)>&& completionHandler) { static constexpr auto apiName = "offscreen.createDocument()"_s; @@ -92,7 +103,7 @@ m_offscreenWebViewWindow = adoptNS([[NSWindow alloc] initWithContentRect:NSZeroRect styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]); [m_offscreenWebViewWindow.get().contentView addSubview:m_offscreenWebView.get()]; #elif PLATFORM(IOS_FAMILY) - m_offscreenWebViewWindow = adoptNS([[UIWindow alloc] initWithFrame:CGRectZero]); + m_offscreenWebViewWindow = adoptNS([[UIWindow alloc] initWithWindowScene:windowScene()]); [m_offscreenWebViewWindow.get() addSubview:m_offscreenWebView.get()]; #endif From d01d085ea0fdef14a17d1c9765c49b8fee40fa67 Mon Sep 17 00:00:00 2001 From: Brent Fulgham Date: Thu, 27 Aug 2026 22:06:14 -0700 Subject: [PATCH 008/103] [CFNetwork] Honor Expires cookie dates that use JS Date.toString() dates (month-before-day Expires values) https://bugs.webkit.org/show_bug.cgi?id=322546 rdar://185840799 Reviewed by Matthew Finkel. CFNetwork's cookie-date parser rejects a cookie-date containing a month-before-day date format. Unfortunately, this is the format JavaScript's Date.prototype.toString() method produces (at least in some locales). This causes cookies with an Expires attribute of that form to be silently ignored, converting the cookie from a persistent value to a session cookie (which disappears when the browser quits). This patch introduces an initial scan for month-first date formats, and fixes up the string if necessary. We perform the date fix-up prior to handing off to CFNetwork (or libSOUP) so that we only have to perform the cookie parsing logic once. The cURL ports never rejected month-before-day strings, so did not suffer from this behavior. While this patch is largely a workaround until CFNetwork and libSOUP fix their underlying implementations, it also allows us to improve behavior for downlevel shipment of Safari as well as Safari Technology Preview. This fix can only cover document.cookie cases. The Set-Cookie response-header path is parsed inside NSURLSession before WebKit sees the response, so there is no interception point; that half is fixed only by rdar://185837942. * LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires.html: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/platform/Cookie.h: * Source/WebCore/platform/network/Cookie.cpp: (WebCore::CookieUtil::isMonthNameToken): (WebCore::CookieUtil::cookieStringWithDayFirstExpires): * Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm: (WebCore::parseDOMCookie): Repair the parsed cookie before adjusting it. * Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp: (WebCore::NetworkStorageSession::setCookiesFromDOM const): Canonical link: https://commits.webkit.org/320016@main --- .../cookies/attributes/expires-expected.txt | 13 +++ .../cookies/attributes/expires.html | 54 +++++++++- .../WebCore/WebCore.xcodeproj/project.pbxproj | 2 + Source/WebCore/platform/Cookie.h | 7 +- Source/WebCore/platform/network/Cookie.cpp | 101 +++++++++++++++++- .../cocoa/NetworkStorageSessionCocoa.mm | 8 +- .../soup/NetworkStorageSessionSoup.cpp | 6 +- 7 files changed, 185 insertions(+), 6 deletions(-) diff --git a/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires-expected.txt index 575b6d486326..66403af11479 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires-expected.txt @@ -4,9 +4,22 @@ PASS Set cookie with expires value followed by comma via HTTP headers PASS Set cookie with future expiration via HTTP headers PASS Set expired cookie along with valid cookie via HTTP headers PASS Don't set cookie with expires set to the past via HTTP headers +PASS Set cookie with the month before the day of the month in expires via HTTP headers +FAIL Don't set cookie with the month before the day of the month in an expires in the past via HTTP headers assert_equals: The cookie was rejected. expected "" but got "test=7" +FAIL Don't set cookie with no day name and the month before the day of the month in an expires in the past via HTTP headers assert_equals: The cookie was rejected. expected "" but got "test=8" +FAIL Don't set cookie with an expires in the past in Date.prototype.toString() format via HTTP headers assert_equals: The cookie was rejected. expected "" but got "test=9" +PASS Set cookie whose Max-Age overrides an expires in the past via HTTP headers +PASS Set cookie with an asctime format expires, whose time precedes the year via HTTP headers PASS Set cookie with expires value containing a comma via document.cookie PASS Set cookie with expires value followed by comma via document.cookie PASS Set cookie with future expiration via document.cookie PASS Set expired cookie along with valid cookie via document.cookie PASS Don't set cookie with expires set to the past via document.cookie +PASS Set cookie with the month before the day of the month in expires via document.cookie +PASS Don't set cookie with the month before the day of the month in an expires in the past via document.cookie +PASS Don't set cookie with no day name and the month before the day of the month in an expires in the past via document.cookie +PASS Don't set cookie with an expires in the past in Date.prototype.toString() format via document.cookie +PASS Set cookie whose Max-Age overrides an expires in the past via document.cookie +PASS Set cookie with an asctime format expires, whose time precedes the year via document.cookie +PASS Don't set cookie with an expires in the past and a non-ASCII timezone comment via document.cookie diff --git a/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires.html b/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires.html index a6bacfd74e97..28a59482b93c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires.html +++ b/LayoutTests/imported/w3c/web-platform-tests/cookies/attributes/expires.html @@ -40,6 +40,49 @@ expected: "", name: "Don't set cookie with expires set to the past", }, + // RFC 6265 section 5.1.1 finds the day of the month, the month and the year by matching each + // date token independently, so either ordering parses: "10 Apr 1980" and "Apr 10 1980" are + // both valid and denote the same date. Month-first is what JavaScript's + // Date.prototype.toString() produces, and sites pass that to document.cookie in place of + // toUTCString(), so interoperability here is important. + // + // Note that only the past-expiration cases below actually distinguish a conforming + // implementation: if the Expires attribute is ignored, the cookie is stored as a session + // cookie and is therefore still present, so a future expiration looks identical either way. + { + cookie: "test=6; Expires=Fri Jan 01 2038 00:00:00 GMT", + expected: "test=6", + name: "Set cookie with the month before the day of the month in expires", + }, + { + cookie: "test=7; Expires=Thu Apr 10 1980 16:33:12 GMT", + expected: "", + name: "Don't set cookie with the month before the day of the month in an expires in the past", + }, + { + cookie: "test=8; Expires=Apr 10 1980 16:33:12 GMT", + expected: "", + name: "Don't set cookie with no day name and the month before the day of the month in an expires in the past", + }, + { + cookie: "test=9; Expires=Thu Apr 10 1980 16:33:12 GMT-0700 (Pacific Daylight Time)", + expected: "", + name: "Don't set cookie with an expires in the past in Date.prototype.toString() format", + }, + { + cookie: "test=10; Expires=Thu Apr 10 1980 16:33:12 GMT; Max-Age=1000", + expected: "test=10", + name: "Set cookie whose Max-Age overrides an expires in the past", + }, + // The asctime form is also month-first, but it puts the time where Date.prototype.toString() + // puts the year: "Thu Apr 10 16:33:12 1980". It must not be reordered as though the token + // after the day of the month were a year, because "Fri 01 Jan 03:14:07 2038" denotes a date in + // the past, which would drop the cookie instead of storing it. + { + cookie: "test=12; Expires=Fri Jan 01 03:14:07 2038", + expected: "test=12", + name: "Set cookie with an asctime format expires, whose time precedes the year", + }, ]; // These tests evaluate setting cookies with expiration via HTTP headers. @@ -51,6 +94,15 @@ for (const test of expiresTests) { domCookieTest(test.cookie, test.expected, test.name + " via document.cookie"); } + + // A timezone name is localized, so it can be non-ASCII. Date.prototype.toString() only reaches + // a cookie through script, and sending non-ASCII in a response header would exercise header + // encoding at the same time, so this case covers document.cookie only. Same date as test=9, + // written with escapes to keep this file ASCII-only. + domCookieTest( + "test=11; Expires=Thu Apr 10 1980 16:33:12 GMT-0700 (\u0398\u03b5\u03c1\u03b9\u03bd\u03ae \u03ce\u03c1\u03b1 \u0395\u03b9\u03c1\u03b7\u03bd\u03b9\u03ba\u03bf\u03cd)", + "", + "Don't set cookie with an expires in the past and a non-ASCII timezone comment via document.cookie"); - \ No newline at end of file + diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj index 7887cffc508b..6f52e2af5f84 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj @@ -15020,6 +15020,7 @@ 7AEAD8AD290289C9008B5675 /* ScrollingTreeOverflowScrollProxyNode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScrollingTreeOverflowScrollProxyNode.cpp; sourceTree = ""; }; 7AEAD8AE290289E1008B5675 /* ScrollingTreeOverflowScrollProxyNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollingTreeOverflowScrollProxyNode.h; sourceTree = ""; }; 7AEFEA122D52A89D007B21AC /* DocumentEnums.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DocumentEnums.h; sourceTree = ""; }; + 7AF0423D303F4F8F006FB27D /* Cookie.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Cookie.cpp; sourceTree = ""; }; 7AF9B1FC18CFB2DF00C64BEF /* VTTRegion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VTTRegion.cpp; sourceTree = ""; }; 7AF9B1FD18CFB2DF00C64BEF /* VTTRegion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTTRegion.h; sourceTree = ""; }; 7AF9B1FE18CFB2DF00C64BEF /* VTTRegion.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VTTRegion.idl; sourceTree = ""; }; @@ -31006,6 +31007,7 @@ E43AF8E41AC5B7DD00CA717E /* CacheValidation.cpp */, E43AF8E51AC5B7DD00CA717E /* CacheValidation.h */, 91B8F0B321953D65000C2B00 /* CertificateSummary.h */, + 7AF0423D303F4F8F006FB27D /* Cookie.cpp */, 7A56996E2086C618000E0433 /* CookieRequestHeaderFieldProxy.h */, E13F01EA1270E10D00DFBA71 /* CookieStorage.h */, 514C76590CE923A1007EF3CD /* Credential.h */, diff --git a/Source/WebCore/platform/Cookie.h b/Source/WebCore/platform/Cookie.h index bef6c7fa47a8..f85e1b8cc959 100644 --- a/Source/WebCore/platform/Cookie.h +++ b/Source/WebCore/platform/Cookie.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2009 Joseph Pecoraro. All rights reserved. - * Copyright (C) 2017-2018 Apple Inc. All rights reserved. + * Copyright (C) 2017-2026 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -26,9 +26,10 @@ #pragma once +#include +#include #include #include -#include #include #ifdef __OBJC__ @@ -127,6 +128,8 @@ namespace CookieUtil { WEBCORE_EXPORT String defaultPathForURL(const URL&); +std::optional cookieStringWithDayFirstExpires(StringView); + } // namespace CookieUtil } // namespace WebCore diff --git a/Source/WebCore/platform/network/Cookie.cpp b/Source/WebCore/platform/network/Cookie.cpp index a056809cf96d..14137546674b 100644 --- a/Source/WebCore/platform/network/Cookie.cpp +++ b/Source/WebCore/platform/network/Cookie.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Apple Inc. All rights reserved. + * Copyright (C) 2017-2026 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -26,6 +26,14 @@ #include "config.h" #include "Cookie.h" +#include +#include +#include +#include +#include +#include +#include + namespace WebCore { #if !PLATFORM(COCOA) @@ -60,6 +68,97 @@ String defaultPathForURL(const URL& url) return path.left(lastSlashPosition); } +static bool isMonthNameToken(StringView token) +{ + // RFC 6265 section 5.1.1 matches a month by its first three characters, case-insensitively. + if (token.length() < 3) + return false; + auto prefix = token.left(3); + return std::ranges::any_of(WTF::monthName, [&](auto month) { + return equalIgnoringASCIICase(prefix, month); + }); +} + +std::optional cookieStringWithDayFirstExpires(StringView cookieString) +{ + auto firstSemicolon = cookieString.find(';'); + if (firstSemicolon == notFound) + return std::nullopt; + + // Locate the Expires value within the original string. The last one wins, per RFC 6265 + // section 5.3. + size_t valueStart = notFound; + size_t valueEnd = notFound; + for (size_t position = firstSemicolon + 1; position <= cookieString.length();) { + auto semicolon = cookieString.find(';', position); + auto attributeEnd = semicolon == notFound ? cookieString.length() : semicolon; + auto attribute = cookieString.substring(position, attributeEnd - position); + if (auto equals = attribute.find('='); equals != notFound) { + if (equalLettersIgnoringASCIICase(attribute.left(equals).trim(isTabOrSpace), "expires"_s)) { + valueStart = position + equals + 1; + valueEnd = attributeEnd; + } + } + if (semicolon == notFound) + break; + position = semicolon + 1; + } + + if (valueStart == notFound) + return std::nullopt; + + // Find a month name immediately followed by a one or two digit day of the month. In a day-first + // value the token after the month is the four digit year, so this does not match and nothing is + // rewritten. + auto isSeparator = [](char16_t character) { + return character == ' ' || character == '\t'; + }; + size_t monthStart = notFound; + size_t monthEnd = notFound; + size_t dayStart = notFound; + size_t dayEnd = notFound; + for (size_t position = valueStart; position < valueEnd;) { + while (position < valueEnd && isSeparator(cookieString[position])) + ++position; + size_t tokenStart = position; + while (position < valueEnd && !isSeparator(cookieString[position])) + ++position; + if (tokenStart == position) + break; + if (monthStart == notFound) { + if (isMonthNameToken(cookieString.substring(tokenStart, position - tokenStart))) { + monthStart = tokenStart; + monthEnd = position; + } + continue; + } + dayStart = tokenStart; + dayEnd = position; + break; + } + + if (monthStart == notFound || dayStart == notFound) + return std::nullopt; + + auto day = cookieString.substring(dayStart, dayEnd - dayStart); + if (day.length() > 2 || !day.containsOnly>()) + return std::nullopt; + + size_t yearStart = dayEnd; + while (yearStart < valueEnd && isSeparator(cookieString[yearStart])) + ++yearStart; + size_t yearEnd = yearStart; + while (yearEnd < valueEnd && !isSeparator(cookieString[yearEnd])) + ++yearEnd; + auto year = cookieString.substring(yearStart, yearEnd - yearStart); + if (year.length() != 4 || !year.containsOnly>()) + return std::nullopt; + + // Return a new string where we have swapped the two tokens. + return makeString(cookieString.left(monthStart), day, cookieString.substring(monthEnd, dayStart - monthEnd), + cookieString.substring(monthStart, monthEnd - monthStart), cookieString.substring(dayEnd)); +} + } // namespace CookieUtil } // namespace WebCore diff --git a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm index 3f913649ece7..1d45007892c8 100644 --- a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm +++ b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2023 Apple Inc. All rights reserved. + * Copyright (C) 2015-2026 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -47,6 +47,7 @@ #import #import #import +#import #import @interface NSURL () @@ -522,6 +523,11 @@ - (CFURLRef)_cfurl; // cookiesWithResponseHeaderFields doesn't parse cookies without a value cookieString = cookieString.contains('=') ? cookieString : makeString(cookieString, '='); + // FIXME: Remove this once CFNetwork's cookie-date parser accepts a date that + // writes the month before the day of the month. RFC 6265 section 5.1.1 accepts either ordering. + if (auto dayFirst = CookieUtil::cookieStringWithDayFirstExpires(cookieString)) + cookieString = WTF::move(*dayFirst); + return adjustScriptWrittenCookie([NSHTTPCookie _cookieForSetCookieString:cookieString.createNSString().get() forURL:cookieURL partition:nsStringNilIfEmpty(partition).get()], cappedLifetime); } diff --git a/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp b/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp index 304d5114057c..7e781ba906d9 100644 --- a/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp +++ b/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp @@ -427,7 +427,11 @@ void NetworkStorageSession::setCookiesFromDOM(const URL& firstParty, const SameS GSList* existingCookies = soup_cookie_jar_get_cookie_list(jar, origin.get(), TRUE); for (auto& cookieString : value.split('\n')) { - GUniquePtr cookie(soup_cookie_parse(cookieString.utf8().data(), origin.get())); + // FIXME: Remove this once libsoup's cookie-date parser accepts a date that writes the month + // before the day of the month. RFC 6265 section 5.1.1 accepts either ordering. + auto dayFirst = CookieUtil::cookieStringWithDayFirstExpires(cookieString); + auto utf8CookieString = (dayFirst ? *dayFirst : cookieString).utf8(); + GUniquePtr cookie(soup_cookie_parse(utf8CookieString.data(), origin.get())); if (!cookie) continue; From 2715812bcdba4065f929c0633a226cf4755fc11d Mon Sep 17 00:00:00 2001 From: Abrar Rahman Protyasha Date: Thu, 27 Aug 2026 22:25:31 -0700 Subject: [PATCH 009/103] REGRESSION(319888@main): [AppKit Gestures] Cannot back/forward navigate with gesture-driven swipe https://bugs.webkit.org/show_bug.cgi?id=322776 rdar://186040671 Reviewed by Wenson Hsieh. 319888@main started clamping the delta of the wheel events produced by the pan GR along any pinned axis, with the intention that a sideways scroll component during a simultaneous pan + transient zoom could not turn into a rubberband stretch (which makes the scrolling tree discard the orthogonal delta for the rest of the gesture). The wheel event in question feeds two systems, though; the swipe tracker maintained by view gesture controller, and the actual page. For the former, our clamping means we remove the signal used to determine PendingSwipeTracker::scrollEventCanBecomeSwipe(), and as such we inadvertently broke swiping -> page navigation when the horizontal axis is pinned, i.e. the common case at the default page scale. In this patch, we build a separate event per consumer. The swipe tracker gets the unclamped delta, and only the event that reaches the page is clamped. A more principled fix would be to make the scrolling tree _not_ discard the orthogonal component of a scroll delta from the motivation for 319888@main, but that is riskier and certainly not right in exactly the form I described. Test: AppKitGesturesTests.Basic.swipingAtPinnedStateShouldTriggerPageNavigation * Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm: (-[WKAppKitGestureController sendWheelEventForGesture:]): * Tools/TestWebKitAPI/Tests/WebKit/WebPage/AppKit Gesture Tests/BasicAppKitGesturesTests.swift: Canonical link: https://commits.webkit.org/320017@main --- .../WKAppKitGestureController.mm | 65 +++++++++++-------- .../BasicAppKitGesturesTests.swift | 41 ++++++++++++ 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm b/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm index a510d16e9c45..9b93b5fa9b8c 100644 --- a/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm +++ b/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm @@ -1483,53 +1483,66 @@ - (void)sendWheelEventForGesture:(NSPanGestureRecognizer *)gesture bool canScrollVertically = [_panGestureRecognizer _canPanVertically] && !(pinnedState.top() && pinnedState.bottom()); gestureDelta = WebCore::FloatSize { _directionalScrollLockTracker->update(gestureDelta, canScrollHorizontally, canScrollVertically, prefersUnlockedScroll, [gesture timestamp]) }; - // FIXME: Fold this into WKDirectionalScrollLockTracker as a hard clamp applied _after_ directional lock heuristics. + // This clamping is a workaround for the fact that if an axis is pinned, + // the scrolling tree turns it into a rubberband, and the orthogonal delta + // is discarded, which stops panning for the rest of the gesture. (if a + // transient zoom + pan sequence occurs simultaneously) + // + // The clamp applies only to the wheel event that reaches the page, though, + // since the swipe tracker has to see the unclamped delta, or else it is + // unable to determine PendingSwipeTracker::scrollEventCanBecomeSwipe(). + // + // FIXME: Fold this into WKDirectionalScrollLockTracker as a hard clamp applied + // _after_ the directional lock heuristics, for the events that reach the page. + auto clampedGestureDelta = gestureDelta; if (!canScrollVertically) - gestureDelta.setHeight(0); + clampedGestureDelta.setHeight(0); if (!canScrollHorizontally) - gestureDelta.setWidth(0); + clampedGestureDelta.setWidth(0); - auto wheelTicks { gestureDelta.scaled(1. / static_cast(WebCore::Scrollbar::pixelsPerLineStep())) }; auto granularity = WebKit::WebWheelEvent::Granularity::ScrollByPixelWheelEvent; bool directionInvertedFromDevice = false; auto phase = toWebEventPhase(gesture.state); auto momentumPhase = WebKit::WebWheelEvent::Phase::None; bool hasPreciseScrollingDeltas = true; uint32_t scrollCount = 1; - auto unacceleratedScrollingDelta = gestureDelta; auto ioHIDEventTimestamp = timestamp; std::optional rawPlatformDelta; auto momentumEndType = WebKit::WebWheelEvent::MomentumEndType::Unknown; - WebKit::WebWheelEvent wheelEvent { - { WebKit::WebEventType::Wheel, { }, timestamp, WTF::UUID::createVersion4() }, - WebCore::IntPoint { position }, - WebCore::IntPoint { globalPosition }, - gestureDelta, - wheelTicks, - granularity, - directionInvertedFromDevice, - phase, - momentumPhase, - hasPreciseScrollingDeltas, - scrollCount, - unacceleratedScrollingDelta, - ioHIDEventTimestamp, - rawPlatformDelta, - momentumEndType, - WebKit::WebEventInputSource::Automation + auto makeWheelEvent = [&](WebCore::FloatSize delta) { + auto wheelTicks { delta.scaled(1. / static_cast(WebCore::Scrollbar::pixelsPerLineStep())) }; + auto unacceleratedScrollingDelta = delta; + return WebKit::NativeWebWheelEvent { + WebKit::WebWheelEvent { + { WebKit::WebEventType::Wheel, { }, timestamp, WTF::UUID::createVersion4() }, + WebCore::IntPoint { position }, + WebCore::IntPoint { globalPosition }, + delta, + wheelTicks, + granularity, + directionInvertedFromDevice, + phase, + momentumPhase, + hasPreciseScrollingDeltas, + scrollCount, + unacceleratedScrollingDelta, + ioHIDEventTimestamp, + rawPlatformDelta, + momentumEndType, + WebKit::WebEventInputSource::Automation + } + }; }; - WebKit::NativeWebWheelEvent nativeEvent { wheelEvent }; - CheckedPtr impl = [webView _impl]; bool forwardToGestureController = impl->allowsBackForwardNavigationGestures() && [self prefersForwardingToGestureController:gesture]; - if (forwardToGestureController && protect(impl->ensureGestureController())->handleScrollWheelEvent(nativeEvent)) { + if (forwardToGestureController && protect(impl->ensureGestureController())->handleScrollWheelEvent(makeWheelEvent(gestureDelta))) { WK_APPKIT_GESTURE_CONTROLLER_RELEASE_LOG_DEBUG([webView _protectedPage]->logIdentifier(), "View gesture controller handled gesture"); return; } - [webView _protectedPage]->handleNativeWheelEvent(nativeEvent); + [webView _protectedPage]->handleNativeWheelEvent(makeWheelEvent(clampedGestureDelta)); } #pragma mark - Momentum Handling diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WebPage/AppKit Gesture Tests/BasicAppKitGesturesTests.swift b/Tools/TestWebKitAPI/Tests/WebKit/WebPage/AppKit Gesture Tests/BasicAppKitGesturesTests.swift index 0e2ffb3fd727..59e25a78be93 100644 --- a/Tools/TestWebKitAPI/Tests/WebKit/WebPage/AppKit Gesture Tests/BasicAppKitGesturesTests.swift +++ b/Tools/TestWebKitAPI/Tests/WebKit/WebPage/AppKit Gesture Tests/BasicAppKitGesturesTests.swift @@ -1254,6 +1254,47 @@ extension AppKitGesturesTests.Basic { #expect(page.backForwardList.backList.count == 1) } + @Test( + .bug("https://webkit.org/b/322776", "Swiping at pinned state should trigger page navigation") + ) + func swipingAtPinnedStateShouldTriggerPageNavigation() async throws { + // Establish a back-forward history entry so that a swiping would have somewhere to navigate to. + try await page.load(URL(string: "about:blank?1")).wait() + let firstPageURL = page.url + + let testURL = try #require(Bundle.testResources.url(forResource: "red", withExtension: "html")) + try await page.load(testURL).wait() + await page.waitForNextPresentationUpdate() + let secondPageURL = page.url + + #expect(page.backForwardList.backList.count == 1) + + let start = screenBounds(ofPointInWindowCoordinates: CGPoint(x: window.frame.width / 4, y: window.frame.height / 2)) + let end = screenBounds(ofPointInWindowCoordinates: CGPoint(x: 3 * window.frame.width / 4, y: window.frame.height / 2)) + + // Swipe right, back navigation. + await recap.play { composer in + composer._wk_scroll(withStart: start, end: end, duration: .seconds(0.5)) + } + + try await Task.sleep(for: .seconds(1)) + + #expect(page.url == firstPageURL) + #expect(page.backForwardList.backList.count == 0) + #expect(page.backForwardList.forwardList.count == 1) + + // Swipe left, forward navigation. + await recap.play { composer in + composer._wk_scroll(withStart: end, end: start, duration: .seconds(0.5)) + } + + try await Task.sleep(for: .seconds(1)) + + #expect(page.url == secondPageURL) + #expect(page.backForwardList.backList.count == 1) + #expect(page.backForwardList.forwardList.count == 0) + } + @Test(arguments: [Duration.zero, .seconds(1)]) func longPressAndDragOnImageSelectsEntireText(delay: Duration) async throws { let baseURL = try #require(Bundle.testResources.resourceURL) From 36058b8e69ac846ca93e33feb106b09d4b2fd787 Mon Sep 17 00:00:00 2001 From: Keith Miller Date: Thu, 27 Aug 2026 22:57:45 -0700 Subject: [PATCH 010/103] [Wasm] Unreachable end ops don't widen result types https://bugs.webkit.org/show_bug.cgi?id=318480 rdar://180535979 Reviewed by Yusuke Suzuki. In 305413.1013@safari-7624.5-branch we fixed how result types were pushed onto the wasm value stack, widening them to the expected type rather than the last predecessor's type at the merge. This missed the case for unreachable expressions in the block, which is fixed in this patch. Originally-landed-as: 305413.1080@safari-7624.5-branch (f65ca49ea4a7). rdar://185368954 Canonical link: https://commits.webkit.org/320018@main --- ...able-end-if-no-else-widens-to-signature.js | 75 +++++++++++++++++++ .../JavaScriptCore/wasm/WasmFunctionParser.h | 34 +++++---- 2 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 JSTests/wasm/stress/unreachable-end-if-no-else-widens-to-signature.js diff --git a/JSTests/wasm/stress/unreachable-end-if-no-else-widens-to-signature.js b/JSTests/wasm/stress/unreachable-end-if-no-else-widens-to-signature.js new file mode 100644 index 000000000000..73af41f67744 --- /dev/null +++ b/JSTests/wasm/stress/unreachable-end-if-no-else-widens-to-signature.js @@ -0,0 +1,75 @@ +// rdar://180535979 +// The unreachable End handler synthesizes an else for `if`-without-`else` and +// installs the saved if-param stack as the (reachable) else-arm result. Those +// values must be widened to the block's declared result types before they +// propagate to the parent stack: an unreachable then-arm may have `br 0`'d a +// value that only inhabits the wider result type. Without widening, BBQ's +// emitRefTestOrCast trusts the stale narrow type and elides the IsCell / +// IsWasmGCObject runtime checks. + +import * as assert from "../assert.js"; + +function uleb128(n) { const r = []; do { let b = n & 0x7f; n >>>= 7; if (n) b |= 0x80; r.push(b); } while (n); return r; } +function encodeString(s) { const b = []; for (let i = 0; i < s.length; i++) b.push(s.charCodeAt(i)); return [...uleb128(b.length), ...b]; } +function section(id, content) { return [id, ...uleb128(content.length), ...content]; } + +function buildModule() { + const typeSection = section(1, [ + 4, + 0x5F, 0x01, 0x7E, 0x01, // type 0: struct { i64 mut } + 0x60, 0x01, 0x64, 0x00, 0x01, 0x6E, // type 1: func (param (ref 0)) (result anyref) + 0x60, 0x03, 0x7F, 0x6F, 0x64, 0x00, 0x01, 0x7E, // type 2: func (i32, externref, (ref 0)) -> i64 + 0x60, 0x00, 0x01, 0x64, 0x00, // type 3: func () -> (ref 0) + ]); + const funcSection = section(3, [0x02, 0x02, 0x03]); + const exportSection = section(7, [0x02, + ...encodeString("test"), 0x00, 0x00, + ...encodeString("make"), 0x00, 0x01]); + + // (func $test (param $cond i32) (param $ext externref) (param $s (ref 0)) (result i64) + // local.get $s + // local.get $cond + // if (param (ref 0)) (result anyref) + // drop + // local.get $ext + // any.convert_extern + // br 0 ;; then-arm goes unreachable + // end ;; <- parseUnreachableExpression()::End, synthetic else + // ref.cast (ref 0) ;; must NOT elide IsCell / IsWasmGCObject checks + // struct.get 0 0) + const body0 = [ + 0x00, + 0x20, 0x02, + 0x20, 0x00, + 0x04, 0x01, + 0x1A, + 0x20, 0x01, + 0xFB, 0x1A, + 0x0C, 0x00, + 0x0B, + 0xFB, 0x16, 0x00, + 0xFB, 0x02, 0x00, 0x00, + 0x0B, + ]; + // (func $make (result (ref 0)) i64.const 0x1234 struct.new 0) + const body1 = [0x00, 0x42, 0xB4, 0x24, 0xFB, 0x00, 0x00, 0x0B]; + const codeSection = section(10, [0x02, + ...uleb128(body0.length), ...body0, + ...uleb128(body1.length), ...body1]); + return new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + ...typeSection, ...funcSection, ...exportSection, ...codeSection]); +} + +const bytes = buildModule(); +assert.truthy(WebAssembly.validate(bytes)); +const instance = new WebAssembly.Instance(new WebAssembly.Module(bytes)); +const struct = instance.exports.make(); + +for (let i = 0; i < wasmTestLoopCount; ++i) { + // cond == 0: synthetic else delivers the (ref 0) param; ref.cast succeeds. + assert.eq(instance.exports.test(0, null, struct), 0x1234n); + // cond == 1: then-arm br's an anyref-wrapped JS number to the if's + // continuation. The post-end value is statically anyref, so ref.cast must + // perform the full runtime check and trap. + assert.throws(() => instance.exports.test(1, 1.5, struct), WebAssembly.RuntimeError, "ref.cast failed to cast reference to target heap type"); +} diff --git a/Source/JavaScriptCore/wasm/WasmFunctionParser.h b/Source/JavaScriptCore/wasm/WasmFunctionParser.h index 4f4cf92ad41f..4361ff3e3bd9 100644 --- a/Source/JavaScriptCore/wasm/WasmFunctionParser.h +++ b/Source/JavaScriptCore/wasm/WasmFunctionParser.h @@ -247,7 +247,13 @@ class FunctionParser : public Parser, public FunctionParserTypes::checkLocalInitialized(uint32_t index) -> PartialRe } template -auto FunctionParser::checkExpressionStack(const ControlType& controlData, bool forceSignature) -> PartialResult +auto FunctionParser::checkBlockFallthrough(const ControlType& controlData, FallThroughStateTag fallthrough) -> PartialResult { const auto& blockSignature = controlData.signature(); const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; @@ -1909,7 +1915,11 @@ auto FunctionParser::checkExpressionStack(const ControlType& controlDat const auto actualType = m_expressionStack[m_currentStackBegin + i].type(); const auto expectedType = blockSignature.returnType(i); WASM_VALIDATOR_FAIL_IF(!isSubtype(actualType, expectedType), "control flow returns with unexpected type. "_s, actualType, " is not a "_s, expectedType); - if (forceSignature) + // The spec requires the output type of a structured control instruction to be + // the result type from its signature, even when the fallthrough value is a subtype. + // FIXME: We should support some sort of abstract interpretation so this can be the + // least upper bound of the merging CFG. + if (fallthrough == MergePoint) m_expressionStack[m_currentStackBegin + i].setType(expectedType); } @@ -3539,7 +3549,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) ControlEntry& controlEntry = m_controlStack.last(); WASM_VALIDATOR_FAIL_IF(!ControlType::isIf(controlEntry.controlData), "else block isn't associated to an if"); - WASM_FAIL_IF_HELPER_FAILS(checkExpressionStack(controlEntry.controlData)); + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); auto ifBranchResults = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); WASM_TRY_ADD_TO_CONTEXT(addElse(controlEntry.controlData, ifBranchResults)); m_expressionStack.shrink(m_currentStackBegin); @@ -3580,7 +3590,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) ControlEntry& controlEntry = m_controlStack.last(); WASM_VALIDATOR_FAIL_IF(!isTryOrCatch(controlEntry.controlData), "catch block isn't associated to a try"); - WASM_FAIL_IF_HELPER_FAILS(checkExpressionStack(controlEntry.controlData)); + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); ResultList results; auto preCatchStack = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); @@ -3606,7 +3616,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) ControlEntry& controlEntry = m_controlStack.last(); WASM_VALIDATOR_FAIL_IF(!isTryOrCatch(controlEntry.controlData), "catch block isn't associated to a try"); - WASM_FAIL_IF_HELPER_FAILS(checkExpressionStack(controlEntry.controlData)); + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); auto preCatchStack = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); WASM_TRY_ADD_TO_CONTEXT(addCatchAll(preCatchStack, controlEntry.controlData)); @@ -3714,7 +3724,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) WASM_VALIDATOR_FAIL_IF(!ControlType::isTry(targetData) && !ControlType::isTopLevel(targetData), "delegate target isn't a try or the top level block"); WASM_TRY_ADD_TO_CONTEXT(addDelegate(targetData, controlEntry.controlData)); - WASM_FAIL_IF_HELPER_FAILS(checkExpressionStack(controlEntry.controlData)); + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); const uint32_t parentBegin = parentEntryBegin(); auto enclosedStack = m_expressionStack.mutableSpan().subspan(parentBegin); @@ -3849,20 +3859,16 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) case End: { ControlEntry data = m_controlStack.takeLast(); if (ControlType::isIf(data.controlData)) { - WASM_FAIL_IF_HELPER_FAILS(checkExpressionStack(data.controlData)); + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(data.controlData, NewSiblingBlock)); auto ifBranchResults = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); WASM_TRY_ADD_TO_CONTEXT(addElse(data.controlData, ifBranchResults)); m_expressionStack.shrink(m_currentStackBegin); m_expressionStack.append(data.elseBlockStack.span()); } - // FIXME: endBlock may modify the expressionStack slice for the result of the block. // That's a little too effectful but we don't have a better API right now. // see: https://bugs.webkit.org/show_bug.cgi?id=164353 - - // The spec requires the output type of a structured control instruction to be - // the result type from its signature, even when the fallthrough value is a subtype. - WASM_FAIL_IF_HELPER_FAILS(checkExpressionStack(data.controlData, true)); + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(data.controlData, MergePoint)); const uint32_t parentBegin = parentEntryBegin(); auto enclosedStack = m_expressionStack.mutableSpan().subspan(parentBegin); @@ -4064,7 +4070,7 @@ auto FunctionParser::parseUnreachableExpression() -> PartialResult WASM_TRY_ADD_TO_CONTEXT(addElseToUnreachable(data.controlData)); m_expressionStack.shrink(m_currentStackBegin); m_expressionStack.append(data.elseBlockStack.span()); - WASM_FAIL_IF_HELPER_FAILS(checkExpressionStack(data.controlData)); + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(data.controlData, MergePoint)); // Reachable End handling: the combined enclosedStack now lives in // m_expressionStack[parentBegin..end]. From befc99869859fc54f2f0931042518f037996252a Mon Sep 17 00:00:00 2001 From: Tyler Wilcock Date: Thu, 27 Aug 2026 23:08:54 -0700 Subject: [PATCH 011/103] AX: In isolated tree mode, AXStringForTextMarkerRange unexpectedly includes a whitespace at a soft line break https://bugs.webkit.org/show_bug.cgi?id=322442 rdar://185725689 Reviewed by Dominic Mazzoni. Layout drops the space a line soft-wraps at, but AccessibilityRenderObject::textRuns re-adds it to the end of the wrapping line's run, so that a range spanning the wrap still reads "foo bar" rather than "foobar". A line's range therefore ended one character past the line's rendered text, and AXStringForTextMarkerRange over the range AXTextMarkerRangeForLine returned included a space that renders on no line, e.g. for

aaa bbb

, "aaa " rather than "aaa". The live tree ends that range before the space. endOfLine() produces an upstream position, and AXTextMarker::operator CharacterOffset() maps an upstream marker through previousCharacterOffset(). With this commit, we do the same in the isolated tree by moving the returned range's end marker back over the wrap space, which also brings its length and end index in line with the live tree's (3, not 4). * LayoutTests/accessibility/isolated-tree/empty-final-line-range-expected.txt: Added. * LayoutTests/accessibility/isolated-tree/empty-final-line-range.html: Added. * LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space-expected.txt: Added. * LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space.html: Added. * Source/WebCore/accessibility/AXTextMarker.cpp: (WebCore::lineRangeWithout): (WebCore::AXTextMarker::characterRangeForLine const): (WebCore::AXTextMarker::markerRangeForLineIndex const): (WebCore::AXTextMarker::lineNumberForIndex const): (WebCore::AXTextMarker::lineRange const): (WebCore::lineRangeWithoutCollapsedTrailingNewline): Deleted. Canonical link: https://commits.webkit.org/320019@main --- .../empty-final-line-range-expected.txt | 10 +++ .../isolated-tree/empty-final-line-range.html | 39 ++++++++++++ ...-at-soft-break-excludes-space-expected.txt | 14 +++++ ...ne-range-at-soft-break-excludes-space.html | 52 ++++++++++++++++ Source/WebCore/accessibility/AXTextMarker.cpp | 62 ++++++++++++++----- 5 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 LayoutTests/accessibility/isolated-tree/empty-final-line-range-expected.txt create mode 100644 LayoutTests/accessibility/isolated-tree/empty-final-line-range.html create mode 100644 LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space-expected.txt create mode 100644 LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space.html diff --git a/LayoutTests/accessibility/isolated-tree/empty-final-line-range-expected.txt b/LayoutTests/accessibility/isolated-tree/empty-final-line-range-expected.txt new file mode 100644 index 000000000000..db2ef58f8e3e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/empty-final-line-range-expected.txt @@ -0,0 +1,10 @@ +This test ensures the line at a text control's last text position is its empty final line. + +PASS: textarea.stringForTextMarkerRange(lineRange) === '' +PASS: textarea.textMarkerRangeLength(lineRange) === 0 +PASS: lineStart.isEqual(lastMarker) === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/empty-final-line-range.html b/LayoutTests/accessibility/isolated-tree/empty-final-line-range.html new file mode 100644 index 000000000000..1ebc94777c4b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/empty-final-line-range.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space-expected.txt b/LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space-expected.txt new file mode 100644 index 000000000000..66ef3fc2be08 --- /dev/null +++ b/LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space-expected.txt @@ -0,0 +1,14 @@ +This test ensures a line ended by soft wrapping excludes the space at the wrap point. + +PASS: lineText(1) === 'aaa' +PASS: lineText(2) === 'bbb' +PASS: webArea.textMarkerRangeLength(lineRange(1)) === 3 +PASS: lineText(3) === 'ccc' +PASS: lineText(4) === 'ddd' +PASS: lineText(5) === 'eee' +PASS: lineText(6) === 'fff' + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space.html b/LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space.html new file mode 100644 index 000000000000..47e6d9557046 --- /dev/null +++ b/LayoutTests/accessibility/mac/line-range-at-soft-break-excludes-space.html @@ -0,0 +1,52 @@ + + + + + + + + + +

aaa bbb

+

ccc ddd

+

eee fff

+ + + + diff --git a/Source/WebCore/accessibility/AXTextMarker.cpp b/Source/WebCore/accessibility/AXTextMarker.cpp index 77f1c59e1133..c428099e016a 100644 --- a/Source/WebCore/accessibility/AXTextMarker.cpp +++ b/Source/WebCore/accessibility/AXTextMarker.cpp @@ -829,21 +829,53 @@ static bool hasEmptyFinalLine(AXIsolatedObject& object, const AXTextRuns& textRu return previousRuns->toStringView().endsWith('\n'); } -// |lineRange| ending where the text control's value ends, rather than where its rendered text does. -// A line that ends with the collapsed trailing newline is one character longer than the line of the -// value it stands for, and for the empty final line that newline is the whole range. Marker walks are -// left the unclamped range, so that empty final line remains enumerable as a line of its own. -static AXTextMarkerRange lineRangeWithoutCollapsedTrailingNewline(const AXTextMarkerRange& lineRange) +enum class LineRangeTrim : uint8_t { + // A text control's value ends before the newline its rendered text ends with. + // This option allows explicit trimming of this collapsed newline. + CollapsedTrailingNewline = 1 << 0, + // The text runs keep the space a line soft-wrapped at, appended to the wrapping line's run, so a + // range spanning the wrap reads "foo bar" rather than "foobar". This space renders on no line, so + // no line ends with it. This option denotes it should be trimmed. + SoftWrapSpace = 1 << 1, +}; + +static AXTextMarkerRange lineRangeWithout(const AXTextMarkerRange& lineRange, OptionSet trims) { auto endMarker = lineRange.end().toTextRunMarker(); RefPtr endObject = endMarker.isolatedObject(); - if (!endObject) + const auto* runs = endObject ? endObject->textRuns() : nullptr; + if (!runs) return lineRange; - auto indexOfCollapsedNewline = offsetOfCollapsedTrailingNewline(*endObject, endObject->textRuns()); - if (!indexOfCollapsedNewline || endMarker.offset() <= *indexOfCollapsedNewline) + unsigned endOffset = endMarker.offset(); + if (trims.contains(LineRangeTrim::CollapsedTrailingNewline)) { + std::optional offsetOfNewline = offsetOfCollapsedTrailingNewline(*endObject, runs); + if (offsetOfNewline && endOffset > *offsetOfNewline) + endOffset = *offsetOfNewline; + } + + if (trims.contains(LineRangeTrim::SoftWrapSpace)) { + // Only a line that ended where this object wrapped has a wrap space, as its end + // sits at the end of a run that another follows. + // + // For example, in this text where _ is a wrap-space and | is the text position: + // aaa_| + // bbb + // We want to trim the space after "aaa" if this option is set. + size_t runIndex = runs->indexForOffset(endOffset, Affinity::Upstream); + bool endsWhereObjectWrapped = runIndex != notFound && runIndex != runs->lastRunIndex() && runs->runLengthSumTo(runIndex) == endOffset; + if (endsWhereObjectWrapped && endOffset && runs->toStringView()[endOffset - 1] == space) + --endOffset; + } + + if (endOffset == endMarker.offset()) + return lineRange; + + if (lineRange.start().objectID() == endMarker.objectID() && lineRange.start().offset() > endOffset) { + // Trimming the range would move the end before the start, so early-exit. return lineRange; - return { lineRange.start(), AXTextMarker { *endObject, *indexOfCollapsedNewline } }; + } + return { lineRange.start(), AXTextMarker { *endObject, endOffset } }; } // Advances |lineRange| to the following line: the range from the start of the next line through @@ -898,7 +930,7 @@ CharacterRange AXTextMarker::characterRangeForLine(unsigned lineIndex) const // the preceding line's range), which is why only block-separated lines were affected. unsigned precedingLength = AXTextMarkerRange { textRunMarker, currentLineRange.start() }.length(); - return CharacterRange(precedingLength, lineRangeWithoutCollapsedTrailingNewline(currentLineRange).length()); + return CharacterRange(precedingLength, lineRangeWithout(currentLineRange, LineRangeTrim::CollapsedTrailingNewline).length()); } AXTextMarkerRange AXTextMarker::markerRangeForLineIndex(unsigned lineIndex) const @@ -916,7 +948,7 @@ AXTextMarkerRange AXTextMarker::markerRangeForLineIndex(unsigned lineIndex) cons currentLineRange = nextLineRange(currentLineRange, IncludeTrailingLineBreak::No, std::nullopt); --lineIndex; } - return lineRangeWithoutCollapsedTrailingNewline(currentLineRange); + return lineRangeWithout(currentLineRange, { LineRangeTrim::CollapsedTrailingNewline, LineRangeTrim::SoftWrapSpace }); } int AXTextMarker::lineNumberForIndex(unsigned index) const @@ -938,7 +970,7 @@ int AXTextMarker::lineNumberForIndex(unsigned index) const unsigned lineNumber = 0; auto currentLineRange = textRunMarker.lineRange(LineRangeType::Current, IncludeTrailingLineBreak::Yes); while (currentLineRange) { - unsigned lineLength = lineRangeWithoutCollapsedTrailingNewline(currentLineRange).length(); + unsigned lineLength = lineRangeWithout(currentLineRange, LineRangeTrim::CollapsedTrailingNewline).length(); auto nextRange = nextLineRange(currentLineRange, IncludeTrailingLineBreak::Yes, stopAtID); // A line occupies the index space up to the start of the next line, which is not the same as // the length of its own range: the newline synthesized at a block boundary belongs to no @@ -1788,7 +1820,7 @@ AXTextMarkerRange AXTextMarker::lineRange(LineRangeType type, IncludeTrailingLin if (type == LineRangeType::Current) { auto startMarker = atLineStart() ? *this : previousLineStart(); auto endMarker = atLineEnd() ? *this : nextLineEnd(includeTrailingLineBreak); - return lineRangeWithoutCollapsedTrailingNewline({ startMarker, endMarker }); + return lineRangeWithout({ startMarker, endMarker }, LineRangeTrim::CollapsedTrailingNewline); } if (type == LineRangeType::Left) { @@ -1798,7 +1830,7 @@ AXTextMarkerRange AXTextMarker::lineRange(LineRangeType type, IncludeTrailingLin startMarker = startMarker.previousLineStart(); auto endMarker = startMarker.nextLineEnd(includeTrailingLineBreak); - return lineRangeWithoutCollapsedTrailingNewline({ WTF::move(startMarker), WTF::move(endMarker) }); + return lineRangeWithout({ WTF::move(startMarker), WTF::move(endMarker) }, LineRangeTrim::CollapsedTrailingNewline); } AX_ASSERT(type == LineRangeType::Right); @@ -1809,7 +1841,7 @@ AXTextMarkerRange AXTextMarker::lineRange(LineRangeType type, IncludeTrailingLin startMarker = startMarker.previousLineStart(); auto endMarker = startMarker.nextLineEnd(includeTrailingLineBreak); - return lineRangeWithoutCollapsedTrailingNewline({ WTF::move(startMarker), WTF::move(endMarker) }); + return lineRangeWithout({ WTF::move(startMarker), WTF::move(endMarker) }, LineRangeTrim::CollapsedTrailingNewline); } AXTextMarkerRange AXTextMarker::wordRange(WordRangeType type) const From 31dc597d97b73503dba0b54721d70db30f0ffb78 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Campos Date: Thu, 27 Aug 2026 23:15:59 -0700 Subject: [PATCH 012/103] [TextureMapper] Remove unused BitmapTexture::Flags::DepthBuffer https://bugs.webkit.org/show_bug.cgi?id=322711 Reviewed by Nikolas Zimmermann. It's unused since 286605@main * Source/WebCore/platform/graphics/egl/BitmapTexture.cpp: (WebCore::BitmapTexture::swapTexture): (WebCore::BitmapTexture::reset): (WebCore::BitmapTexture::initializeStencil): (WebCore::BitmapTexture::createFboIfNeeded): (WebCore::BitmapTexture::bindAsSurface): (WebCore::BitmapTexture::~BitmapTexture): (WebCore::BitmapTexture::initializeDepthBuffer): Deleted. (WebCore::depthBufferFormat): Deleted. * Source/WebCore/platform/graphics/egl/BitmapTexture.h: * Source/WebCore/platform/graphics/egl/BitmapTexturePool.cpp: (WebCore::BitmapTexturePool::acquireTexture): Canonical link: https://commits.webkit.org/320020@main --- .../platform/graphics/egl/BitmapTexture.cpp | 72 +++---------------- .../platform/graphics/egl/BitmapTexture.h | 15 ++-- .../graphics/egl/BitmapTexturePool.cpp | 1 - 3 files changed, 16 insertions(+), 72 deletions(-) diff --git a/Source/WebCore/platform/graphics/egl/BitmapTexture.cpp b/Source/WebCore/platform/graphics/egl/BitmapTexture.cpp index a5a49fa657b6..f392b5ec9088 100644 --- a/Source/WebCore/platform/graphics/egl/BitmapTexture.cpp +++ b/Source/WebCore/platform/graphics/egl/BitmapTexture.cpp @@ -109,15 +109,6 @@ unsigned BitmapTexture::textureFormat() const return m_flags.contains(Flags::UseBGRALayout) ? GL_BGRA : GL_RGBA; } -static GLenum depthBufferFormat() -{ - auto* glContext = GLContext::current(); - if (glContext->version() >= 300 || glContext->glExtensions().OES_packed_depth_stencil) - return GL_DEPTH24_STENCIL8; - - return GL_DEPTH_COMPONENT16; -} - BitmapTexture::BitmapTexture(const IntSize& size, OptionSet flags) : m_flags(flags) , m_size(size) @@ -241,8 +232,6 @@ BitmapTexture::BitmapTexture(EGLImage image, const IntSize& size, OptionSet flags) m_pixelFormat = flags.contains(Flags::UseBGRALayout) ? PixelFormat::BGRA8 : PixelFormat::RGBA8; m_filterOperation = nullptr; - if (!flags.contains(Flags::DepthBuffer)) { - if (m_fbo) { - glDeleteFramebuffers(1, &m_fbo); - m_fbo = 0; - } - - if (m_depthBufferObject) { - glDeleteRenderbuffers(1, &m_depthBufferObject); - m_depthBufferObject = 0; - } - - if (m_stencilBufferObject) { - glDeleteRenderbuffers(1, &m_stencilBufferObject); - m_stencilBufferObject = 0; - } + if (m_fbo) { + glDeleteFramebuffers(1, &m_fbo); + m_fbo = 0; + } - m_stencilBound = false; - m_clipStack = { }; + if (m_stencilBufferObject) { + glDeleteRenderbuffers(1, &m_stencilBufferObject); + m_stencilBufferObject = 0; } + m_stencilBound = false; + m_clipStack = { }; + if (m_size == size) return; m_size = size; @@ -446,19 +428,6 @@ void BitmapTexture::updateContents(GraphicsLayer* sourceLayer, const IntRect& ta void BitmapTexture::initializeStencil() { - if (m_flags.contains(Flags::DepthBuffer)) { - // We have a depth buffer and we're asked to have a stencil buffer as well. This is only - // possible if packed depth stencil is available. If that's the case, just bind the depth - // buffer as the stencil one if haven't done so. If packed depth stencil is not available - // don't do anything, which will cause stencil clips on this surface to fail. - if (depthBufferFormat() == GL_DEPTH24_STENCIL8 && !m_stencilBound) { - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferObject); - m_stencilBound = true; - } - return; - } - - // We don't have a depth buffer. Use a stencil only buffer. if (m_stencilBufferObject) return; @@ -471,18 +440,6 @@ void BitmapTexture::initializeStencil() glClear(GL_STENCIL_BUFFER_BIT); } -void BitmapTexture::initializeDepthBuffer() -{ - if (m_depthBufferObject) - return; - - glGenRenderbuffers(1, &m_depthBufferObject); - glBindRenderbuffer(GL_RENDERBUFFER, m_depthBufferObject); - glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat(), m_size.width(), m_size.height()); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferObject); -} - void BitmapTexture::clearIfNeeded() { if (!m_shouldClear) @@ -504,8 +461,6 @@ void BitmapTexture::createFboIfNeeded() glGenFramebuffers(1, &m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_renderTarget, id(), 0); - if (m_flags.contains(Flags::DepthBuffer)) - initializeDepthBuffer(); m_shouldClear = true; } @@ -515,10 +470,6 @@ void BitmapTexture::bindAsSurface() createFboIfNeeded(); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glViewport(0, 0, m_size.width(), m_size.height()); - if (m_flags.contains(Flags::DepthBuffer)) - glEnable(GL_DEPTH_TEST); - else - glDisable(GL_DEPTH_TEST); clearIfNeeded(); m_clipStack.apply(); } @@ -530,9 +481,6 @@ BitmapTexture::~BitmapTexture() if (m_fbo) glDeleteFramebuffers(1, &m_fbo); - if (m_depthBufferObject) - glDeleteRenderbuffers(1, &m_depthBufferObject); - if (m_stencilBufferObject) glDeleteRenderbuffers(1, &m_stencilBufferObject); } diff --git a/Source/WebCore/platform/graphics/egl/BitmapTexture.h b/Source/WebCore/platform/graphics/egl/BitmapTexture.h index 04a0dfe1ac6c..a0be5630add3 100644 --- a/Source/WebCore/platform/graphics/egl/BitmapTexture.h +++ b/Source/WebCore/platform/graphics/egl/BitmapTexture.h @@ -62,15 +62,14 @@ class BitmapTexture final : public ThreadSafeRefCounted { public: enum class Flags : uint8_t { SupportsAlpha = 1 << 0, - DepthBuffer = 1 << 1, #if USE(GBM) - BackedByDMABuf = 1 << 2, - ForceLinearBuffer = 1 << 3, - ForceVivanteSuperTiledBuffer = 1 << 4, + BackedByDMABuf = 1 << 1, + ForceLinearBuffer = 1 << 2, + ForceVivanteSuperTiledBuffer = 1 << 3, #endif - UseBGRALayout = 1 << 5, - NearestFiltering = 1 << 6, - ExternalOESRenderTarget = 1 << 7, + UseBGRALayout = 1 << 4, + NearestFiltering = 1 << 5, + ExternalOESRenderTarget = 1 << 6, }; static Ref create(const IntSize& size, OptionSet flags = { }) @@ -94,7 +93,6 @@ class BitmapTexture final : public ThreadSafeRefCounted { void bindAsSurface(); void initializeStencil(); - void initializeDepthBuffer(); uint32_t id() const { return m_id; } void updateContents(NativeImage*, const IntRect&, const IntPoint& offset); @@ -149,7 +147,6 @@ class BitmapTexture final : public ThreadSafeRefCounted { unsigned m_renderTarget { 0 }; unsigned m_binding { 0 }; unsigned m_fbo { 0 }; - unsigned m_depthBufferObject { 0 }; unsigned m_stencilBufferObject { 0 }; bool m_stencilBound { false }; bool m_shouldClear { true }; diff --git a/Source/WebCore/platform/graphics/egl/BitmapTexturePool.cpp b/Source/WebCore/platform/graphics/egl/BitmapTexturePool.cpp index 1210c3378ff2..36206e6f966e 100644 --- a/Source/WebCore/platform/graphics/egl/BitmapTexturePool.cpp +++ b/Source/WebCore/platform/graphics/egl/BitmapTexturePool.cpp @@ -80,7 +80,6 @@ Ref BitmapTexturePool::acquireTexture(const IntSize& size, Option && entry.texture->flags().contains(BitmapTexture::Flags::ForceVivanteSuperTiledBuffer) == flags.contains(BitmapTexture::Flags::ForceVivanteSuperTiledBuffer) #endif && entry.texture->flags().contains(BitmapTexture::Flags::UseBGRALayout) == flags.contains(BitmapTexture::Flags::UseBGRALayout) - && entry.texture->flags().contains(BitmapTexture::Flags::DepthBuffer) == flags.contains(BitmapTexture::Flags::DepthBuffer) && entry.texture->flags().contains(BitmapTexture::Flags::NearestFiltering) == flags.contains(BitmapTexture::Flags::NearestFiltering); }); From cec9d207c1b775717dcdd9b63460b99d7907e661 Mon Sep 17 00:00:00 2001 From: Anne van Kesteren Date: Thu, 27 Aug 2026 23:17:29 -0700 Subject: [PATCH 013/103] :target should keep matching a target element that is removed and reinserted https://bugs.webkit.org/show_bug.cgi?id=322720 Reviewed by Tim Nguyen. A Document's target element is only set by the scroll to the fragment algorithm; nothing sets it to null when the element is removed from the document. WebKit cleared it on removal since 2008 (23144@main) to avoid a dangling raw pointer, which is no longer a concern now that m_cssTarget is a WeakPtr. Gecko already behaved this way and Chromium made the same change in 2024, so this aligns all engines with the HTML Standard. Tests: imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption.html imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target.html WPT PR: https://github.com/web-platform-tests/wpt/pull/62238 Canonical link: https://commits.webkit.org/320021@main --- .../scroll-to-fragid/WEB_FEATURES.yml | 7 ++-- .../target-pseudo-after-adoption-expected.txt | 4 +++ .../target-pseudo-after-adoption.html | 32 +++++++++++++++++++ ...rget-pseudo-after-reinsertion-expected.txt | 2 +- .../scroll-to-fragid/w3c-import.log | 1 + ...-with-fragment-removed-target-expected.txt | 3 ++ ...document-with-fragment-removed-target.html | 27 ++++++++++++++++ .../the-autofocus-attribute/w3c-import.log | 1 + Source/WebCore/dom/Element.cpp | 3 -- 9 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/WEB_FEATURES.yml b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/WEB_FEATURES.yml index 416ef6049d11..01c1930231a7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/WEB_FEATURES.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/WEB_FEATURES.yml @@ -1,4 +1,3 @@ -features: -- name: target - files: - - target-pseudo-after-reinsertion.html +rules: +- target-pseudo-after-adoption.html: [target] +- target-pseudo-after-reinsertion.html: [target] diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption-expected.txt new file mode 100644 index 000000000000..fe6572753e47 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption-expected.txt @@ -0,0 +1,4 @@ +target + +PASS :target should follow the target element back after a round trip through another document. + diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption.html b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption.html new file mode 100644 index 000000000000..47f36fb2c515 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption.html @@ -0,0 +1,32 @@ + + + + + + + +
target
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-reinsertion-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-reinsertion-expected.txt index a20570c30f17..8fff1f013c79 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-reinsertion-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-reinsertion-expected.txt @@ -1,4 +1,4 @@ target -FAIL :target should match the target element even after it is removed and reinserted. assert_equals: :target should match after reinsertion. expected Element node
target
but got null +PASS :target should match the target element even after it is removed and reinserted. diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/w3c-import.log index db65fbb587e7..249a4f149930 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/w3c-import.log @@ -38,4 +38,5 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html /LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-id-top.html /LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-top.html +/LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-adoption.html /LayoutTests/imported/w3c/web-platform-tests/html/browsers/browsing-the-web/scroll-to-fragid/target-pseudo-after-reinsertion.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target-expected.txt new file mode 100644 index 000000000000..4c9fbed9a341 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target-expected.txt @@ -0,0 +1,3 @@ + +PASS Autofocus should be skipped when the target element has been removed from the document. + diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target.html b/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target.html new file mode 100644 index 000000000000..5433fdf51ec6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target.html @@ -0,0 +1,27 @@ + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/w3c-import.log index 8f389afad2c9..65243ba9ca00 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/w3c-import.log @@ -21,6 +21,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/autofocus-on-stable-document.html /LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-empty.html /LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-nonexistent.html +/LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-removed-target.html /LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-top.html /LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-valid.html /LayoutTests/imported/w3c/web-platform-tests/html/interaction/focus/the-autofocus-attribute/first-reconnected.html diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp index 7155ab9d506a..11e8a70e9e56 100644 --- a/Source/WebCore/dom/Element.cpp +++ b/Source/WebCore/dom/Element.cpp @@ -3293,9 +3293,6 @@ void Element::removingSteps(RemovalType removalType, ContainerNode& oldParentOfR if (isInTopLayer()) [[unlikely]] removeFromTopLayer(); - if (oldDocument->cssTarget() == this) - oldDocument->setCSSTarget(nullptr); - if (isDefinedCustomElement()) [[unlikely]] CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(*this); } From 3b492a1ce76278e13f709737c5606545de35d189 Mon Sep 17 00:00:00 2001 From: Antti Koivisto Date: Thu, 27 Aug 2026 23:52:45 -0700 Subject: [PATCH 014/103] [css-mixins-1] Registered function arguments should evaluate in calling context https://bugs.webkit.org/show_bug.cgi?id=322710 rdar://185975255 Reviewed by Sam Weinig. Implement https://github.com/w3c/csswg-drafts/issues/14338 This mostly affects cycle detection: @function --double(--len ) returns { result: calc(var(--len) * 2); } font-size: --double(1em); used to be cycle because `em` unit was resolved in function context where we can't reference the property we are computing. In calling context it evaluates against parent font-size which is fine. A major benefit is that this allows significant code simplifications. We no longer need to instantiate a separate Style::Builder to resolve arguments. Arguments are now computed directly against the calling element, so first-valid() over the argument and the default becomes a plain loop instead of a synthesized -internal-first-valid() token stream that had to be substituted a second time. The hypothetical element the body is applied to now inherits a font, since font-relative units resolve against the element being styled rather than the parent style. A default can reference an earlier parameter, so parameters are tracked while they resolve. They are all seeded first, so referencing a later one gives the guaranteed-invalid value rather than the calling element's property of that name. A in a default is looked up in the scope the function was defined in. Tests: imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope.html imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping.html imported/w3c/web-platform-tests/css/css-mixins/function-relative-units.html * LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative.html: * Source/WebCore/style/StyleBuilder.cpp: (WebCore::Style::Builder::resolveCustomPropertyValue): (WebCore::Style::Builder::computeCustomPropertyValueForSyntax): * Source/WebCore/style/StyleBuilder.h: * Source/WebCore/style/StyleSubstitutionResolver.cpp: (WebCore::Style::SubstitutionResolver::propertyValueForVariableName): (WebCore::Style::SubstitutionResolver::resolveAndRegisterDashedFunctionArguments): (WebCore::Style::SubstitutionResolver::substituteDashedFunction): (WebCore::Style::createFirstValidVariableData): Deleted. * Source/WebCore/style/StyleSubstitutionResolver.h: Canonical link: https://commits.webkit.org/320022@main --- .../function-definition-scope-expected.txt | 5 + .../css-mixins/function-definition-scope.html | 71 +++++++ .../function-parameter-scoping-expected.txt | 6 + .../function-parameter-scoping.html | 78 +++++++ .../function-relative-units-expected.txt | 13 ++ .../css-mixins/function-relative-units.html | 192 +++++++++++++++++ ...-in-custom-function.tentative-expected.txt | 7 + .../random-in-custom-function.tentative.html | 56 +++++ Source/WebCore/style/StyleBuilder.cpp | 15 +- Source/WebCore/style/StyleBuilder.h | 3 + .../style/StyleSubstitutionResolver.cpp | 201 ++++++++++-------- .../WebCore/style/StyleSubstitutionResolver.h | 11 +- 12 files changed, 559 insertions(+), 99 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope-expected.txt new file mode 100644 index 000000000000..a8215c9167be --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope-expected.txt @@ -0,0 +1,5 @@ + +PASS Parameter default resolves a dashed-function in the definition scope +PASS result resolves a dashed-function in the definition scope +PASS A local resolves a dashed-function in the definition scope + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope.html new file mode 100644 index 000000000000..fe496499bade --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-definition-scope.html @@ -0,0 +1,71 @@ + +Custom Functions: parameter defaults resolve names in the definition scope + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping-expected.txt new file mode 100644 index 000000000000..af79aa29711b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping-expected.txt @@ -0,0 +1,6 @@ + +PASS Default referencing an earlier parameter +PASS Default referencing a later parameter is guaranteed-invalid +PASS Default referencing a later parameter does not see the calling element +PASS Default referencing itself does not see the calling element + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping.html new file mode 100644 index 000000000000..048cfdbdc6cd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-parameter-scoping.html @@ -0,0 +1,78 @@ + +Custom Functions: name scoping in parameter defaults + + + + + + + +
+
+
+
+ + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units-expected.txt new file mode 100644 index 000000000000..6d7bb5db743d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units-expected.txt @@ -0,0 +1,13 @@ + +PASS em in a typed parameter +PASS rem in a typed parameter +PASS em within calc() in a typed parameter +PASS em in a typed parameter, untyped return +PASS em in a typed parameter default +PASS em in a typed parameter of a nested function +PASS em in a typed result +PASS em within calc() in a typed result +PASS em in an untyped local substituted into a typed result +PASS em in an untyped parameter +PASS em in an untyped result + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units.html new file mode 100644 index 000000000000..b783527ba19e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-relative-units.html @@ -0,0 +1,192 @@ + +Custom Functions: font-relative units in parameters, locals and result + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative-expected.txt index b341c138fc4d..f8f2486a6595 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative-expected.txt @@ -7,4 +7,11 @@ PASS random() in same registered function same locals different positions FAIL random() in different function same default locals assert_true: expected true got false FAIL random() in function in local overrides argument assert_false: Random values should not be equal expected false got true PASS random() in function argument +PASS random(fixed) outside a function +PASS random(fixed) through an untyped argument +PASS random(fixed) through a typed argument +FAIL random(fixed) through an untyped argument into a typed result assert_equals: expected "50" but got "0" +FAIL random(fixed) in an untyped local substituted into a typed result assert_equals: expected "50" but got "0" +FAIL random(fixed) in a typed parameter default assert_equals: expected "50" but got "0" +PASS random(fixed) written in a typed result diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative.html index 818f48c33165..bd68306c0386 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-values/random-in-custom-function.tentative.html @@ -67,6 +67,29 @@ @function --g-number() returns { result: random(--foo property-index-scoped, 1, 1e6); } + + /* random(fixed ) bypasses the random cache name entirely, so the + cases below test only whether a value is produced at all, independent of + how cache names are scoped inside custom functions. Each resolves to 50. */ + @function --fixed-untyped(--x) { + result: var(--x); + } + @function --fixed-typed-argument(--x ) returns { + result: var(--x); + } + @function --fixed-untyped-argument-typed-result(--x) returns { + result: var(--x); + } + @function --fixed-untyped-local-typed-result() returns { + --x: random(fixed 0.5, 0, 100); + result: var(--x); + } + @function --fixed-typed-default(--x : random(fixed 0.5, 0, 100)) returns { + result: var(--x); + } + @function --fixed-typed-result() returns { + result: random(fixed 0.5, 0, 100); + } diff --git a/Source/WebCore/style/StyleBuilder.cpp b/Source/WebCore/style/StyleBuilder.cpp index e855e2309b1c..e28f7acb16ce 100644 --- a/Source/WebCore/style/StyleBuilder.cpp +++ b/Source/WebCore/style/StyleBuilder.cpp @@ -775,7 +775,14 @@ std::optional Builder::resolveCustomPropertyVa if (!registered) return { { CustomProperty::createForVariableData(name, *resolvedData) } }; - auto dependencies = CSSPropertyParser::collectParsedCustomPropertyValueDependencies(registered->syntax, resolvedData->tokens(), resolvedData->context()); + return computeCustomPropertyValueForSyntax(name, registered->syntax, *resolvedData); +} + +// Parses an already-substituted value against a syntax and computes it on this builder's element. +// Shared by registered custom properties and by custom function parameters. +std::optional Builder::computeCustomPropertyValueForSyntax(const AtomString& name, const CSSCustomPropertySyntax& syntax, const CSSVariableData& resolvedData) +{ + auto dependencies = CSSPropertyParser::collectParsedCustomPropertyValueDependencies(syntax, resolvedData.tokens(), resolvedData.context()); // https://drafts.css-houdini.org/css-properties-values-api/#dependency-cycles bool hasCycles = false; @@ -803,18 +810,18 @@ std::optional Builder::resolveCustomPropertyVa if (isFontDependent) m_state->updateFont(); - auto isAttrTainted = resolvedData->isAttrTainted(); + auto isAttrTainted = resolvedData.isAttrTainted(); // https://drafts.csswg.org/css-values-5/#attr-security // A registered custom property with or syntax resolved from attr()-tainted data is IACVT. if (isAttrTainted == IsAttrTainted::Yes) { - for (auto& component : registered->syntax.definition) { + for (auto& component : syntax.definition) { if (component.type == CSSCustomPropertySyntax::Type::URL || component.type == CSSCustomPropertySyntax::Type::Image) return { }; } } - return CSSPropertyParser::parseTypedCustomPropertyValue(name, registered->syntax, resolvedData->tokens(), m_state, resolvedData->context(), isAttrTainted); + return CSSPropertyParser::parseTypedCustomPropertyValue(name, syntax, resolvedData.tokens(), m_state, resolvedData.context(), isAttrTainted); } void Builder::applyPageSizeDescriptor(CSSValue& value) diff --git a/Source/WebCore/style/StyleBuilder.h b/Source/WebCore/style/StyleBuilder.h index 43147d182ba3..3212e5451379 100644 --- a/Source/WebCore/style/StyleBuilder.h +++ b/Source/WebCore/style/StyleBuilder.h @@ -32,7 +32,9 @@ namespace WebCore { class CSSCustomPropertyValue; +class CSSVariableData; enum class CSSWideKeyword : uint8_t; +struct CSSCustomPropertySyntax; struct CSSRegisteredCustomProperty; namespace Style { @@ -63,6 +65,7 @@ class Builder { RefPtr resolveCustomPropertyForContainerQueries(const CSSCustomPropertyValue&); std::optional resolveFunctionResult(); + std::optional computeCustomPropertyValueForSyntax(const AtomString&, const CSSCustomPropertySyntax&, const CSSVariableData&); BuilderState& state() { return m_state; } const MatchResult& matchResult() const { return m_cascade.matchResult(); } diff --git a/Source/WebCore/style/StyleSubstitutionResolver.cpp b/Source/WebCore/style/StyleSubstitutionResolver.cpp index 37682c5b6446..3b7a71400823 100644 --- a/Source/WebCore/style/StyleSubstitutionResolver.cpp +++ b/Source/WebCore/style/StyleSubstitutionResolver.cpp @@ -71,6 +71,8 @@ #include "StyleResolver.h" #include "StyleScope.h" #include +#include +#include namespace WebCore { namespace Style { @@ -108,29 +110,11 @@ static CSSParserTokenRange unwrapArgumentBraces(CSSParserTokenRange argument) return range.atEnd() ? contents : argument; } -static Ref createFirstValidVariableData(std::span> candidates, const CSSParserContext& context) -{ - // Uses the internal -internal-first-valid name rather than the public first-valid(). The public - // function must validate against any property's grammar, which is not implemented yet. See the - // FIXME on substituteFirstValid(). - Vector tokens; - tokens.append(CSSParserToken(FunctionToken, "-internal-first-valid"_s, CSSParserToken::BlockStart)); - bool isFirst = true; - for (auto& candidate : candidates) { - if (!isFirst) - tokens.append(CSSParserToken(CommaToken)); - isFirst = false; - tokens.append(candidate); - } - tokens.append(CSSParserToken(RightParenthesisToken, CSSParserToken::BlockEnd)); - return CSSVariableData::create(CSSParserTokenRange { tokens }, context); -} - void SubstitutionResolver::propagateAttrTaint(IsAttrTainted isAttrTainted, std::span tokens) { if (isAttrTainted != IsAttrTainted::Yes) return; - m_isAttrTainted = true; + m_isAttrTainted = IsAttrTainted::Yes; if (isInURLContext() || containsURLTokens(tokens)) m_hasTaintedURL = true; } @@ -146,6 +130,14 @@ RefPtr SubstitutionResolver::propertyValueForVariableName( if (functionId == CSSValueEnv) return m_styleBuilder.state().document().styleScope().environmentVariables().values().get(variableName); + // Every parameter of the function whose arguments are being resolved shadows the calling element's + // custom property of the same name, resolved or not, so a default can reference an earlier + // parameter but never sees the calling element's value of a later one. + if (!m_parameterValues.isEmpty() && functionId == CSSValueVar) { + if (auto parameter = m_parameterValues.last().getOptional(variableName)) + return *parameter; + } + // Apply this variable first, in case it is still unresolved m_styleBuilder.applyCustomProperty(variableName); @@ -189,10 +181,11 @@ auto SubstitutionResolver::substituteVarArgumentGrammar(CSSParserTokenRange rang // https://drafts.csswg.org/css-values-5/#attr-security // Isolate the flag to see whether resolving the name itself involved attr()-tainted values. Diffing // it would miss the taint when something earlier in the same value had already set it. - auto wasAttrTainted = std::exchange(m_isAttrTainted, false); + auto wasAttrTainted = std::exchange(m_isAttrTainted, IsAttrTainted::No); auto substitutedName = substituteTokenRange(nameArgRange, context); - auto isNameAttrTainted = m_isAttrTainted ? IsAttrTainted::Yes : IsAttrTainted::No; - m_isAttrTainted |= wasAttrTainted; + auto isNameAttrTainted = m_isAttrTainted; + if (wasAttrTainted == IsAttrTainted::Yes) + m_isAttrTainted = IsAttrTainted::Yes; if (!substitutedName) return { { }, fallbackRange, isNameAttrTainted }; @@ -360,9 +353,10 @@ bool SubstitutionResolver::substituteInheritFunction(CSSParserTokenRange range, } // https://drafts.csswg.org/css-mixins/#evaluate-a-custom-function -// Registers each parameter with its type, resolves argument styles, then updates registrations -// to universal syntax with resolved values as initial values. -// Returns resolved argument properties to prepend to the body rule, or nullptr on failure. +// Computes each parameter from its argument, falling back to the default when there is no argument or +// the argument does not match the parameter's type. Values are computed against the calling element. +// Registers each parameter as universal with its computed value, and returns the properties to prepend +// to the body rule, or nullptr on failure. RefPtr SubstitutionResolver::resolveAndRegisterDashedFunctionArguments(const Vector& parameters, const Vector>& arguments, LocalPropertyRegistry& registrations, ScopeOrdinal definitionScope) { // A parameter without a default requires a corresponding argument. A missing one makes the whole @@ -372,82 +366,95 @@ RefPtr SubstitutionResolver::resolveAndRegisterDashedFun return nullptr; } - // "For each function parameter, create a custom property registration with the parameter's type." - auto argumentRegistrations = LocalPropertyRegistry { }; - for (auto& parameter : parameters) { - argumentRegistrations.add({ - .name = AtomString { parameter.name }, - .syntax = parameter.type, - .inherits = true, - }); - } + auto& context = m_substitutionValue->context(); + + // A default may invoke another custom function, which resolves its own parameters, so these need a + // frame of their own rather than a single set. + m_parameterValues.append({ }); + auto popParameterValues = makeScopeExit([&] { + m_parameterValues.removeLast(); + }); + // Seeded before any is resolved, so that a default referencing a parameter that is not resolved + // yet gets the guaranteed-invalid value instead of the calling element's property of that name. + for (auto& parameter : parameters) + m_parameterValues.last().set(parameter.name, nullptr); + + SetForScope scopedLookupScope(m_dashedFunctionLookupScope, definitionScope); + + auto resolvedArgumentProperties = MutableStyleProperties::create(); - // "Let argument rule be an initially empty style rule" with first-valid(arg value, default value) for each parameter. - auto argumentRule = MutableStyleProperties::create(); for (auto [i, parameter] : indexedRange(parameters)) { bool hasArgument = i < arguments.size() && !arguments[i].isEmpty(); - // first-valid(arg value, default value). A parameter with neither is omitted from the rule, - // resolving to the guaranteed-invalid value via its (valueless) registration. - auto candidates = [&] { - Vector, 2> candidates; - if (hasArgument) - candidates.append(arguments[i].span()); - if (parameter.defaultValue) - candidates.append(parameter.defaultValue->tokens().span()); - return candidates; - }(); - if (candidates.isEmpty()) - continue; + // Computes a candidate against the parameter's type, or returns null if it does not match. + auto computeCandidate = [&](std::span candidateTokens) -> RefPtr { + if (candidateTokens.empty()) + return nullptr; - // A bare CSS-wide keyword (e.g. a parameter defaulting to `inherit`) keeps its keyword semantics - // rather than becoming a literal value. https://drafts.csswg.org/css-mixins/#evaluating-custom-functions - auto primaryTokens = CSSParserTokenRange { candidates.first() }; - primaryTokens.consumeWhitespace(); - if (auto keyword = CSSPropertyParserHelpers::consumeCSSWideKeyword(primaryTokens); keyword && primaryTokens.atEnd()) { - argumentRule->addParsedProperty({ CSSPropertyCustom, CSSCustomPropertyValue::createWithCSSWideKeyword(parameter.name, *keyword) }); - continue; - } + auto data = CSSVariableData::create(CSSParserTokenRange { candidateTokens }, m_isAttrTainted, context); - // Fast path: an untyped parameter with an argument needs no first-valid(). The argument is - // already substituted and, being non-empty, is always valid for the universal syntax, so it - // wins outright and the default is irrelevant. - if (hasArgument && parameter.type.isUniversal()) { - auto value = CSSCustomPropertyValue::createSyntaxAll(parameter.name, CSSVariableData::create(arguments[i], m_substitutionValue->context())); - argumentRule->addParsedProperty({ CSSPropertyCustom, WTF::move(value) }); - continue; - } + if (parameter.type.isUniversal()) + return CustomProperty::createForVariableData(parameter.name, WTF::move(data)); - auto firstValidData = createFirstValidVariableData(candidates.span(), m_substitutionValue->context()); - auto value = CSSCustomPropertyValue::createUnresolved(parameter.name, CSSSubstitutionValue::create(WTF::move(firstValidData))); - argumentRule->addParsedProperty({ CSSPropertyCustom, WTF::move(value) }); - } + // https://drafts.csswg.org/css-values-5/#first-valid + if (!CSSPropertyParser::isValidCustomPropertyValueForSyntax(parameter.type, data->tokenRange(), context)) + return nullptr; - // "Resolve function styles using custom function, argument rule, registrations, and calling context." - // The hypothetical element acts as a child of the calling element, inheriting its computed custom - // properties on demand, so defaults like `var(--caller-prop)` or `inherit` resolve against it. - auto argumentMatchResult = MatchResult::create(); - argumentMatchResult->authorDeclarations.append({ .properties = WTF::move(argumentRule), .styleScopeOrdinal = definitionScope }); + auto computed = m_styleBuilder.computeCustomPropertyValueForSyntax(parameter.name, parameter.type, data); + if (!computed) + return nullptr; - auto builderContext = BuilderContext { - .document = m_styleBuilder.state().document(), - .parentStyle = &m_styleBuilder.state().style(), - .element = m_styleBuilder.state().element(), - .localPropertyRegistry = &argumentRegistrations, - .callingContextBuilder = &m_styleBuilder - }; + return WTF::switchOn(*computed, + [](const Ref& property) -> RefPtr { + return property.ptr(); + }, + // A declared type cannot be a CSS-wide keyword. + [](CSSWideKeyword) -> RefPtr { + return nullptr; + }); + }; - auto argumentStyles = Style::ComputedStyle::createPtr(); - Builder argumentBuilder(*argumentStyles, WTF::move(builderContext), argumentMatchResult); - argumentBuilder.state().addGuardedFunctionContexts(m_styleBuilder.state()); - for (auto& parameter : parameters) - argumentBuilder.applyCustomProperty(parameter.name); + auto resolvedValue = [&] -> RefPtr { + // A bare CSS-wide keyword keeps its keyword semantics rather than becoming a literal value. + // https://drafts.csswg.org/css-mixins/#evaluating-custom-functions + // An argument that failed to substitute is empty, and may have no default to fall back to. + auto primaryTokens = [&] -> CSSParserTokenRange { + if (hasArgument) + return CSSParserTokenRange { arguments[i].span() }; + if (parameter.defaultValue) + return parameter.defaultValue->tokenRange(); + return { }; + }(); + primaryTokens.consumeWhitespace(); + if (auto keyword = CSSPropertyParserHelpers::consumeCSSWideKeyword(primaryTokens); keyword && primaryTokens.atEnd()) { + // `initial` is the parameter's registered initial value, which does not exist yet at this + // point, and any keyword other than `inherit` is guaranteed-invalid. + if (*keyword != CSSWideKeyword::Inherit) + return nullptr; + // `inherit` resolves like inherit() with the parameter name, reinterpreted with the + // parameter's type. + RefPtr inherited = propertyValueForVariableName(parameter.name, CSSValueInherit); + if (!inherited || inherited->isGuaranteedInvalid()) + return nullptr; + return computeCandidate(inherited->tokens()); + } + + // first-valid(arg value, default value): the default is used when there is no argument, or + // when the argument does not match the parameter's type. + // The argument is already substituted; a default is not. + if (hasArgument) { + if (RefPtr value = computeCandidate(arguments[i].span())) + return value; + } + if (parameter.defaultValue) { + if (auto substituted = substituteTokenRange(parameter.defaultValue->tokenRange(), context)) + return computeCandidate(substituted->span()); + } + return nullptr; + }(); + + m_parameterValues.last().set(parameter.name, resolvedValue); - // "Set its initial value to the corresponding value in argument styles, set its syntax to the universal syntax definition, - // and prepend a custom property to body rule with the property name and value in argument styles." - auto resolvedArgumentProperties = MutableStyleProperties::create(); - for (auto& parameter : parameters) { - RefPtr resolvedValue = argumentStyles->customPropertyValue(parameter.name); registrations.add({ .name = AtomString { parameter.name }, .syntax = CSSCustomPropertySyntax::universal(), @@ -456,7 +463,7 @@ RefPtr SubstitutionResolver::resolveAndRegisterDashedFun }); if (resolvedValue && !resolvedValue->isGuaranteedInvalid()) { - auto tokenData = CSSVariableData::create(CSSParserTokenRange { resolvedValue->tokens() }, resolvedValue->isAttrTainted(), m_substitutionValue->context()); + auto tokenData = CSSVariableData::create(CSSParserTokenRange { resolvedValue->tokens() }, resolvedValue->isAttrTainted(), context); auto value = CSSCustomPropertyValue::createSyntaxAll(parameter.name, WTF::move(tokenData)); resolvedArgumentProperties->addParsedProperty({ CSSPropertyCustom, WTF::move(value) }); } @@ -472,7 +479,10 @@ bool SubstitutionResolver::substituteDashedFunction(StringView functionName, CSS if (!m_styleBuilder.state().element()) return false; - auto scopedFunctionName = ScopedName { functionName.toAtomString(), m_styleBuilder.state().styleScopeOrdinal() }; + // A in a parameter default resolves in the scope the enclosing function was + // defined in, not the calling element's scope. + auto scopeOrdinal = m_dashedFunctionLookupScope.value_or(m_styleBuilder.state().styleScopeOrdinal()); + auto scopedFunctionName = ScopedName { functionName.toAtomString(), scopeOrdinal }; CheckedPtr element = m_styleBuilder.state().element(); auto resolved = resolveTreeScopedReference(*element, scopedFunctionName, [](const Scope& scope, const ScopedName& scopedName) -> std::optional, ScopeOrdinal>> { @@ -603,6 +613,9 @@ bool SubstitutionResolver::substituteDashedFunction(StringView functionName, CSS }; auto bodyStyles = Style::ComputedStyle::createPtr(); + // Font-relative units in the body resolve against the hypothetical element's own font, so it has to + // inherit one. Custom properties are resolved lazily through the calling context's builder instead. + bodyStyles->inheritIgnoringCustomPropertiesFrom(protect(m_styleBuilder.state().style())); Builder bodyBuilder(*bodyStyles, WTF::move(builderContext), bodyMatchResult); bodyBuilder.state().addGuardedFunctionContexts(m_styleBuilder.state()); @@ -1314,7 +1327,7 @@ bool SubstitutionResolver::isBaseAppearance() RefPtr SubstitutionResolver::substitute(const CSSSubstitutionValue& value) { - m_isAttrTainted = false; + m_isAttrTainted = IsAttrTainted::No; m_hasTaintedURL = false; m_randomItemAutoIndex = 0; m_substitutionValue = &value; @@ -1332,7 +1345,7 @@ RefPtr SubstitutionResolver::substitute(const CSSSubstitutionVa return nullptr; } - auto data = CSSVariableData::create(*substitutedTokens, m_isAttrTainted ? IsAttrTainted::Yes : IsAttrTainted::No, context); + auto data = CSSVariableData::create(*substitutedTokens, m_isAttrTainted, context); m_intermediateTokenStrings.clear(); m_intermediateCustomProperties.clear(); return data; diff --git a/Source/WebCore/style/StyleSubstitutionResolver.h b/Source/WebCore/style/StyleSubstitutionResolver.h index 4ae2435f2c71..611e03296c4e 100644 --- a/Source/WebCore/style/StyleSubstitutionResolver.h +++ b/Source/WebCore/style/StyleSubstitutionResolver.h @@ -116,11 +116,20 @@ class SubstitutionResolver { Builder& m_styleBuilder; const CSSRegisteredCustomProperty* m_registration { nullptr }; RefPtr m_substitutionValue; + // The scope to look up a name in, while resolving the parameter defaults of a + // custom function: names there refer to the scope the function was defined in, not the calling + // element's. + std::optional m_dashedFunctionLookupScope; + // Parameters of the custom functions whose arguments are currently being resolved, innermost last. + // A parameter appears once resolved, so a default can reference an earlier one. A null value is the + // guaranteed-invalid value. Present names shadow the calling element's custom properties. + using ParameterValues = HashMap>; + Vector m_parameterValues; Vector m_intermediateTokenStrings; Vector> m_intermediateCustomProperties; unsigned m_urlContextDepth { 0 }; unsigned m_randomItemAutoIndex { 0 }; - bool m_isAttrTainted { false }; + IsAttrTainted m_isAttrTainted { IsAttrTainted::No }; bool m_hasTaintedURL { false }; }; From 2257a2c9dda1a6fb73c4fe4a93fb7936374020bf Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Fri, 28 Aug 2026 00:09:58 -0700 Subject: [PATCH 015/103] [css-overflow-4] Support `text-overflow: ` https://bugs.webkit.org/show_bug.cgi?id=27545 rdar://94415937 Reviewed by Alan Baradlay. Add support for the value of text-overflow, alongside the existing clip and ellipsis keywords, per the css-overflow-4 spec. text-overflow now mirrors block-ellipsis's representation: Style::TextOverflow is a variant-like type holding either the clip/ellipsis keywords or a custom string, replacing the old boolean TextOverflow enum. Rendering was updated so InlineDisplayLineBuilder::applyEllipsisIfNeeded reads the custom string from style when truncating overflowing inline content, the same way it already does for block-ellipsis. The generated ellipsis TextRun now also carries the line's actual base direction instead of defaulting to LTR, so a custom ellipsis string composed of mixed-direction characters is bidi-resolved against the correct paragraph direction (matters for RTL paragraphs and unicode-bidi: plaintext content whose resolved direction differs from the dir attribute). * LayoutTests/TestExpectations: * LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-computed-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-valid-expected.txt: * Source/WebCore/Headers.cmake: * Source/WebCore/Sources.txt: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/css/CSSProperties.json: * Source/WebCore/html/HTMLInputElement.cpp: (WebCore::HTMLInputElement::shouldTruncateText const): (WebCore::HTMLInputElement::invalidateStyleOnFocusChangeIfNeeded): (WebCore::HTMLInputElement::createInnerTextStyle): * Source/WebCore/html/shadow/TextControlInnerElements.cpp: (WebCore::TextControlInnerElement::resolveCustomStyle): (WebCore::TextControlPlaceholderElement::resolveCustomStyle): * Source/WebCore/layout/formattingContexts/inline/InlineFormattingUtils.cpp: (WebCore::Layout::InlineFormattingUtils::lineEndingTruncationPolicy): * Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp: (WebCore::Layout::InlineDisplayLineBuilder::applyEllipsisIfNeeded): * Source/WebCore/layout/integration/LayoutIntegrationBoxTreeUpdater.cpp: (WebCore::LayoutIntegration::BoxTreeUpdater::adjustStyleIfNeeded): * Source/WebCore/layout/integration/inline/InlineIteratorLineBoxModernPath.h: (WebCore::InlineIterator::LineBoxIteratorModernPath::ellipsisText const): * Source/WebCore/rendering/HitTestResult.cpp: (WebCore::HitTestResult::innerTextIfTruncated const): * Source/WebCore/rendering/TextAutoSizing.cpp: (WebCore::TextAutoSizingHashTranslator::hash): * Source/WebCore/rendering/style/RenderStyleConstants.cpp: * Source/WebCore/rendering/style/RenderStyleConstants.h: * Source/WebCore/style/computed/StyleComputedStyleBase.h: * Source/WebCore/style/computed/data/StyleNonInheritedMiscData.cpp: (WebCore::Style::NonInheritedMiscData::NonInheritedMiscData): (WebCore::Style::NonInheritedMiscData::operator== const): (WebCore::Style::NonInheritedMiscData::dumpDifferences const): * Source/WebCore/style/computed/data/StyleNonInheritedMiscData.h: * Source/WebCore/style/values/overflow/StyleTextOverflow.cpp: Added. (WebCore::Style::CSSValueConversion::operator): * Source/WebCore/style/values/overflow/StyleTextOverflow.h: Added. (WebCore::Style::TextOverflow::TextOverflow): (WebCore::Style::TextOverflow::isClip const): (WebCore::Style::TextOverflow::isEllipsis const): (WebCore::Style::TextOverflow::isString const): (WebCore::Style::TextOverflow::switchOn const): * Source/WebCore/style/values/primitives/StyleKeyword+Mappings.h: * LayoutTests/imported/w3c/web-platform-tests/css/css-typed-om/the-stylepropertymap/properties/text-overflow-expected.txt: Canonical link: https://commits.webkit.org/320023@main --- LayoutTests/TestExpectations | 12 +-- .../text-overflow-computed-expected.txt | 6 +- .../parsing/text-overflow-valid-expected.txt | 6 +- .../properties/text-overflow-expected.txt | 2 +- Source/WebCore/Headers.cmake | 1 + Source/WebCore/Sources.txt | 1 + .../WebCore/WebCore.xcodeproj/project.pbxproj | 6 ++ Source/WebCore/css/CSSProperties.json | 6 +- Source/WebCore/html/HTMLInputElement.cpp | 11 ++- .../html/shadow/TextControlInnerElements.cpp | 7 +- .../inline/InlineFormattingUtils.cpp | 2 +- .../display/InlineDisplayLineBuilder.cpp | 15 +++- .../LayoutIntegrationBoxTreeUpdater.cpp | 2 +- .../inline/InlineIteratorLineBoxModernPath.h | 6 +- Source/WebCore/rendering/HitTestResult.cpp | 2 +- Source/WebCore/rendering/TextAutoSizing.cpp | 6 +- .../rendering/style/RenderStyleConstants.cpp | 9 -- .../rendering/style/RenderStyleConstants.h | 6 -- .../style/computed/StyleComputedStyleBase.h | 2 +- .../data/StyleNonInheritedMiscData.cpp | 10 +-- .../computed/data/StyleNonInheritedMiscData.h | 3 +- .../values/overflow/StyleTextOverflow.cpp | 55 ++++++++++++ .../style/values/overflow/StyleTextOverflow.h | 90 +++++++++++++++++++ .../values/primitives/StyleKeyword+Mappings.h | 6 -- 24 files changed, 211 insertions(+), 61 deletions(-) create mode 100644 Source/WebCore/style/values/overflow/StyleTextOverflow.cpp create mode 100644 Source/WebCore/style/values/overflow/StyleTextOverflow.h diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index e649b41a63ee..148f3ff4168c 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -4312,7 +4312,6 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixe imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-017.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-004.tentative.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-008.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-text-overflow-string-003.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-040.html [ ImageOnlyFailure ] @@ -5429,15 +5428,8 @@ webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-zoom.html [ ImageOnlyFailure ] webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-underscore-001.html [ ImageOnlyFailure ] -# Missing text-overflow: -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-001.html [ ImageOnlyFailure ] -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-002.html [ ImageOnlyFailure ] -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-003.html [ ImageOnlyFailure ] -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-004.html [ ImageOnlyFailure ] -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-005.html [ ImageOnlyFailure ] -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-006.html [ ImageOnlyFailure ] -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-007.html [ ImageOnlyFailure ] -webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-008.html [ ImageOnlyFailure ] +# text-overflow should take in account unicode-bidi to determine ellipsis position. +imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-008.html webkit.org/b/214387 imported/w3c/web-platform-tests/svg/animations/seeking-events-4.html [ Pass Failure ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-computed-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-computed-expected.txt index e20a35252395..4194f4bc6160 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-computed-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-computed-expected.txt @@ -1,7 +1,7 @@ PASS Property text-overflow value 'clip' PASS Property text-overflow value 'ellipsis' -FAIL Property text-overflow value '""' assert_true: '""' is a supported value for text-overflow. expected true got false -FAIL Property text-overflow value '"-"' assert_true: '"-"' is a supported value for text-overflow. expected true got false -FAIL Property text-overflow value '"marker string"' assert_true: '"marker string"' is a supported value for text-overflow. expected true got false +PASS Property text-overflow value '""' +PASS Property text-overflow value '"-"' +PASS Property text-overflow value '"marker string"' diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-valid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-valid-expected.txt index 6da8efaf3cc8..7d238f2b3c1b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-valid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/text-overflow-valid-expected.txt @@ -1,7 +1,7 @@ PASS e.style['text-overflow'] = "clip" should set the property value PASS e.style['text-overflow'] = "ellipsis" should set the property value -FAIL e.style['text-overflow'] = "\"\"" should set the property value assert_not_equals: property should be set got disallowed value "" -FAIL e.style['text-overflow'] = "\"-\"" should set the property value assert_not_equals: property should be set got disallowed value "" -FAIL e.style['text-overflow'] = "\"marker string\"" should set the property value assert_not_equals: property should be set got disallowed value "" +PASS e.style['text-overflow'] = "\"\"" should set the property value +PASS e.style['text-overflow'] = "\"-\"" should set the property value +PASS e.style['text-overflow'] = "\"marker string\"" should set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-typed-om/the-stylepropertymap/properties/text-overflow-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-typed-om/the-stylepropertymap/properties/text-overflow-expected.txt index e18deeb40d5e..e69633d96be6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-typed-om/the-stylepropertymap/properties/text-overflow-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-typed-om/the-stylepropertymap/properties/text-overflow-expected.txt @@ -34,6 +34,6 @@ PASS Setting 'text-overflow' to a transform: translate(50%, 50%) throws TypeErro PASS Setting 'text-overflow' to a transform: perspective(10em) throws TypeError PASS Setting 'text-overflow' to a transform: translate3d(0px, 1px, 2px) translate(0px, 1px) rotate3d(1, 2, 3, 45deg) rotate(45deg) scale3d(1, 2, 3) scale(1, 2) skew(1deg, 1deg) skewX(1deg) skewY(45deg) perspective(1px) matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) matrix(1, 2, 3, 4, 5, 6) throws TypeError FAIL 'text-overflow' does not support 'clip ellipsis' assert_class_string: Unsupported value must be a CSSStyleValue and not one of its subclasses expected "[object CSSStyleValue]" but got "[object Undefined]" -FAIL 'text-overflow' does not support '"..."' assert_class_string: Unsupported value must be a CSSStyleValue and not one of its subclasses expected "[object CSSStyleValue]" but got "[object Undefined]" +PASS 'text-overflow' does not support '"..."' FAIL 'text-overflow' does not support 'fade(1px, 50%)' assert_class_string: Unsupported value must be a CSSStyleValue and not one of its subclasses expected "[object CSSStyleValue]" but got "[object Undefined]" diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake index c698f21f331c..d094dd5a5aac 100644 --- a/Source/WebCore/Headers.cmake +++ b/Source/WebCore/Headers.cmake @@ -3438,6 +3438,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS style/values/overflow/StyleOverflowClipMargin.h style/values/overflow/StyleScrollBehavior.h style/values/overflow/StyleScrollbarGutter.h + style/values/overflow/StyleTextOverflow.h style/values/page/StylePageSize.h diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt index 1638a7ceee11..c442b83be6f3 100644 --- a/Source/WebCore/Sources.txt +++ b/Source/WebCore/Sources.txt @@ -3529,6 +3529,7 @@ style/values/non-standard/StyleWebKitTouchCallout.cpp style/values/outline/StyleOutlineOffset.cpp @header:RenderStyleGetters style/values/overflow/StyleBlockEllipsis.cpp style/values/overflow/StyleScrollbarGutter.cpp +style/values/overflow/StyleTextOverflow.cpp style/values/overflow/StyleOverflowClipMargin.cpp style/values/page/StylePageSize.cpp style/values/pointerevents/StyleTouchAction.cpp diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj index 6f52e2af5f84..8686501f17ae 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj @@ -6322,6 +6322,7 @@ DDB04F3A278E5539008D3678 /* libWTF.a in Product Dependencies */ = {isa = PBXBuildFile; fileRef = DDB04F39278E5531008D3678 /* libWTF.a */; }; DDBF8DAF2AB51FC100B3318A /* RubyFormattingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DDBF8DAE2AB51FC100B3318A /* RubyFormattingContext.h */; }; DDCF765D2C7F85E2005DDAFF /* StyleBlockEllipsis.h in Headers */ = {isa = PBXBuildFile; fileRef = DDCF765C2C7F85E2005DDAFF /* StyleBlockEllipsis.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 17B4998B9CB9379F392A38E0 /* StyleTextOverflow.h in Headers */ = {isa = PBXBuildFile; fileRef = AA570E42DB220D6821FE7A82 /* StyleTextOverflow.h */; settings = {ATTRIBUTES = (Private, ); }; }; DDD517EC2A9FC5440069AF81 /* LineLayoutResult.h in Headers */ = {isa = PBXBuildFile; fileRef = DDD517EB2A9FC5440069AF81 /* LineLayoutResult.h */; settings = {ATTRIBUTES = (Private, ); }; }; DDD5F55C2C703DFC00E1C692 /* TextBoxTrimmer.h in Headers */ = {isa = PBXBuildFile; fileRef = DDD5F55B2C703DFC00E1C692 /* TextBoxTrimmer.h */; }; DDDFE83E2846ECE2006F1EE5 /* cocoa in Copy PDF.js Extras */ = {isa = PBXBuildFile; fileRef = DDDFE83B2846ECD3006F1EE5 /* cocoa */; }; @@ -20227,6 +20228,7 @@ BC95FC0C2C838E9C00051FAE /* CSSPropertyParserConsumer+Filter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSSPropertyParserConsumer+Filter.h"; sourceTree = ""; }; BC95FC0D2C838E9C00051FAE /* CSSPropertyParserConsumer+Filter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "CSSPropertyParserConsumer+Filter.cpp"; sourceTree = ""; }; BC966B372E274F790028A7AF /* StyleBlockEllipsis.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StyleBlockEllipsis.cpp; sourceTree = ""; }; + 2481C500948CA9B768D48AC0 /* StyleTextOverflow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StyleTextOverflow.cpp; sourceTree = ""; }; BC966B402E280AA30028A7AF /* StyleCounterStyle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StyleCounterStyle.h; sourceTree = ""; }; BC96DB420F3A880E00573CB3 /* RenderBoxModelObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderBoxModelObject.h; sourceTree = ""; }; BC96DB450F3A882200573CB3 /* RenderBoxModelObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderBoxModelObject.cpp; sourceTree = ""; }; @@ -21974,6 +21976,7 @@ DDBF8DAE2AB51FC100B3318A /* RubyFormattingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RubyFormattingContext.h; sourceTree = ""; }; DDBF8DB02AB51FD100B3318A /* RubyFormattingContext.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RubyFormattingContext.cpp; sourceTree = ""; }; DDCF765C2C7F85E2005DDAFF /* StyleBlockEllipsis.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StyleBlockEllipsis.h; sourceTree = ""; }; + AA570E42DB220D6821FE7A82 /* StyleTextOverflow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StyleTextOverflow.h; sourceTree = ""; }; DDD517EB2A9FC5440069AF81 /* LineLayoutResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LineLayoutResult.h; sourceTree = ""; }; DDD5F55A2C703DF200E1C692 /* TextBoxTrimmer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TextBoxTrimmer.cpp; sourceTree = ""; }; DDD5F55B2C703DFC00E1C692 /* TextBoxTrimmer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextBoxTrimmer.h; sourceTree = ""; }; @@ -38609,6 +38612,8 @@ A39F8E222A1ECC1E00967C09 /* StyleScrollbarGutter.cpp */, A39F8E212A1ECBFC00967C09 /* StyleScrollbarGutter.h */, BC0929A82E2AA1D6002ACB2C /* StyleScrollBehavior.h */, + 2481C500948CA9B768D48AC0 /* StyleTextOverflow.cpp */, + AA570E42DB220D6821FE7A82 /* StyleTextOverflow.h */, ); path = overflow; sourceTree = ""; @@ -49427,6 +49432,7 @@ BC6120262ECA437D00904592 /* StyleTextEmphasisPosition.h in Headers */, BCD9EFED2E18E5A000D1C3DF /* StyleTextEmphasisStyle.h in Headers */, BCD9EFEB2E18E58600D1C3DF /* StyleTextIndent.h in Headers */, + 17B4998B9CB9379F392A38E0 /* StyleTextOverflow.h in Headers */, BC3CC22E2DE8D2D50032971C /* StyleTextShadow.h in Headers */, BC09298A2E29F25B002ACB2C /* StyleTextSizeAdjust.h in Headers */, BC4631102EA310B600EA911F /* StyleTextSpacingTrim.h in Headers */, diff --git a/Source/WebCore/css/CSSProperties.json b/Source/WebCore/css/CSSProperties.json index e9f212cb81ef..1e9516b9212e 100644 --- a/Source/WebCore/css/CSSProperties.json +++ b/Source/WebCore/css/CSSProperties.json @@ -8696,9 +8696,9 @@ ], "codegen-properties": { "computed-style-storage-path": ["m_nonInheritedData", "miscData"], - "computed-style-storage-kind": "enum", - "computed-style-type": "TextOverflow", - "parser-grammar": "<>" + "computed-style-storage-kind": "reference", + "computed-style-type": "Style::TextOverflow", + "parser-grammar": "clip | ellipsis | " }, "specification": { "category": "css-overflow", diff --git a/Source/WebCore/html/HTMLInputElement.cpp b/Source/WebCore/html/HTMLInputElement.cpp index fe5426df7e1e..d8f93e7469c8 100644 --- a/Source/WebCore/html/HTMLInputElement.cpp +++ b/Source/WebCore/html/HTMLInputElement.cpp @@ -2269,7 +2269,7 @@ bool HTMLInputElement::shouldTruncateText(const Style::ComputedStyle& style) con { if (!isTextField()) return false; - return document().focusedElement() != this && style.textOverflow() == TextOverflow::Ellipsis; + return document().focusedElement() != this && style.textOverflow().isEllipsis(); } void HTMLInputElement::invalidateStyleOnFocusChangeIfNeeded() @@ -2277,7 +2277,7 @@ void HTMLInputElement::invalidateStyleOnFocusChangeIfNeeded() if (!isTextField()) return; // Focus change may affect the result of shouldTruncateText(). - if (CheckedPtr style = renderStyle(); style && style->textOverflow() == TextOverflow::Ellipsis) + if (CheckedPtr style = renderStyle(); style && style->textOverflow().isEllipsis()) invalidateStyleForSubtree(); } @@ -2368,7 +2368,10 @@ Style::ComputedStyle HTMLInputElement::createInnerTextStyle(const Style::Compute textBlockStyle.setOverflowWrap(OverflowWrap::Normal); textBlockStyle.setOverflowX(Overflow::Hidden); textBlockStyle.setOverflowY(Overflow::Hidden); - textBlockStyle.setTextOverflow(shouldTruncateText(style) ? TextOverflow::Ellipsis : TextOverflow::Clip); + if (shouldTruncateText(style)) + textBlockStyle.setTextOverflow(CSS::Keyword::Ellipsis { }); + else + textBlockStyle.setTextOverflow(CSS::Keyword::Clip { }); textBlockStyle.setDisplay(Style::DisplayType::BlockFlow); @@ -2376,7 +2379,7 @@ Style::ComputedStyle HTMLInputElement::createInnerTextStyle(const Style::Compute textBlockStyle.setDisplay(Style::DisplayType::InlineFlowRoot); textBlockStyle.setLogicalMaxWidth(100_css_percentage); textBlockStyle.setColor(Color::black.colorWithAlphaByte(153)); - textBlockStyle.setTextOverflow(TextOverflow::Clip); + textBlockStyle.setTextOverflow(CSS::Keyword::Clip { }); textBlockStyle.setMaskLayers(Style::MaskLayer { autoFillStrongPasswordMaskImage() }); // A stacking context is needed for the mask. if (textBlockStyle.usedZIndex().isAuto()) diff --git a/Source/WebCore/html/shadow/TextControlInnerElements.cpp b/Source/WebCore/html/shadow/TextControlInnerElements.cpp index 71ffa68faea4..a1446f0a07f5 100644 --- a/Source/WebCore/html/shadow/TextControlInnerElements.cpp +++ b/Source/WebCore/html/shadow/TextControlInnerElements.cpp @@ -141,7 +141,7 @@ std::optional TextControlInnerElement::resolveCustomStyl if (isStrongPasswordTextField(shadowHost())) { newStyle->setFlexShrink(0); - newStyle->setTextOverflow(TextOverflow::Clip); + newStyle->setTextOverflow(CSS::Keyword::Clip { }); newStyle->setOverflowX(Overflow::Hidden); newStyle->setOverflowY(Overflow::Hidden); @@ -241,7 +241,10 @@ std::optional TextControlPlaceholderElement::resolveCust styleStyle->setDisplay(controlElement->isPlaceholderVisible() ? Style::DisplayType::BlockFlow : Style::DisplayType::None); if (RefPtr inputElement = dynamicDowncast(controlElement)) { - styleStyle->setTextOverflow(inputElement->shouldTruncateText(*shadowHostStyle) ? TextOverflow::Ellipsis : TextOverflow::Clip); + if (inputElement->shouldTruncateText(*shadowHostStyle)) + styleStyle->setTextOverflow(CSS::Keyword::Ellipsis { }); + else + styleStyle->setTextOverflow(CSS::Keyword::Clip { }); styleStyle->setPaddingTop(0_css_px); styleStyle->setPaddingBottom(0_css_px); } diff --git a/Source/WebCore/layout/formattingContexts/inline/InlineFormattingUtils.cpp b/Source/WebCore/layout/formattingContexts/inline/InlineFormattingUtils.cpp index 3d79bf56ab18..ba051368915e 100644 --- a/Source/WebCore/layout/formattingContexts/inline/InlineFormattingUtils.cpp +++ b/Source/WebCore/layout/formattingContexts/inline/InlineFormattingUtils.cpp @@ -601,7 +601,7 @@ LineEndingTruncationPolicy InlineFormattingUtils::lineEndingTruncationPolicy(con } // Truncation is in effect when the block container has overflow other than visible. - if (rootStyle.overflowX() != Overflow::Visible && rootStyle.textOverflow() == TextOverflow::Ellipsis) + if (rootStyle.overflowX() != Overflow::Visible && !rootStyle.textOverflow().isClip()) return LineEndingTruncationPolicy::WhenContentOverflowsInInlineDirection; return LineEndingTruncationPolicy::NoTruncation; } diff --git a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp index 2fa8843e048d..3d614bad3977 100644 --- a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp +++ b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp @@ -494,7 +494,20 @@ std::optional InlineDisplayLineBuilder::applyElli return { }; auto ellipsisText = [&] -> AtomString { - if (truncationPolicy == LineEndingTruncationPolicy::WhenContentOverflowsInInlineDirection || isLegacyLineClamp) { + if (truncationPolicy == LineEndingTruncationPolicy::WhenContentOverflowsInInlineDirection) { + return WTF::switchOn(displayBoxes[0].layoutBox().style().textOverflow(), + [&](const CSS::Keyword::Clip&) -> AtomString { + return nullAtom(); + }, + [&](const CSS::Keyword::Ellipsis&) -> AtomString { + return TextUtil::ellipsisTextInInlineDirection(displayLine.isHorizontal()); + }, + [&](const Style::String& string) -> AtomString { + return AtomString { string.value }; + } + ); + } + if (isLegacyLineClamp) { // Legacy line clamp always uses ... return TextUtil::ellipsisTextInInlineDirection(displayLine.isHorizontal()); } diff --git a/Source/WebCore/layout/integration/LayoutIntegrationBoxTreeUpdater.cpp b/Source/WebCore/layout/integration/LayoutIntegrationBoxTreeUpdater.cpp index 9b12110248d2..99a81dcc4111 100644 --- a/Source/WebCore/layout/integration/LayoutIntegrationBoxTreeUpdater.cpp +++ b/Source/WebCore/layout/integration/LayoutIntegrationBoxTreeUpdater.cpp @@ -190,7 +190,7 @@ void BoxTreeUpdater::adjustStyleIfNeeded(const RenderElement& renderer, Style::C CheckedRef anonBlockParentStyle = renderer.parent()->style(); // overflow and text-overflow property values don't get forwarded to anonymous block boxes. // e.g.
this text should have ellipsis
- styleToAdjust.setTextOverflow(anonBlockParentStyle->textOverflow()); + styleToAdjust.setTextOverflow(Style::TextOverflow { anonBlockParentStyle->textOverflow() }); styleToAdjust.setOverflowX(anonBlockParentStyle->overflowX()); styleToAdjust.setOverflowY(anonBlockParentStyle->overflowY()); } diff --git a/Source/WebCore/layout/integration/inline/InlineIteratorLineBoxModernPath.h b/Source/WebCore/layout/integration/inline/InlineIteratorLineBoxModernPath.h index d6b752f8c486..8afec3af7c6f 100644 --- a/Source/WebCore/layout/integration/inline/InlineIteratorLineBoxModernPath.h +++ b/Source/WebCore/layout/integration/inline/InlineIteratorLineBoxModernPath.h @@ -55,7 +55,11 @@ class LineBoxIteratorModernPath { bool hasEllipsis() const { return line().hasEllipsis(); } FloatRect ellipsisVisualRectIgnoringBlockDirection() const { return lineEllipsis().visualRect; } - TextRun ellipsisText() const { return TextRun { lineEllipsis().text.string() }; } + TextRun ellipsisText() const + { + return TextRun { lineEllipsis().text.string(), 0, 0, ExpansionBehavior::defaultBehavior(), + line().isLeftToRightInlineDirection() ? TextDirection::LTR : TextDirection::RTL }; + } float contentLogicalTopAdjustedForPrecedingLineBox() const { diff --git a/Source/WebCore/rendering/HitTestResult.cpp b/Source/WebCore/rendering/HitTestResult.cpp index 1fd91f26213e..6e5e610bd97f 100644 --- a/Source/WebCore/rendering/HitTestResult.cpp +++ b/Source/WebCore/rendering/HitTestResult.cpp @@ -377,7 +377,7 @@ String HitTestResult::innerTextIfTruncated(TextDirection& dir) const continue; if (auto* block = dynamicDowncast(element->renderer())) { - if (block->style().textOverflow() == TextOverflow::Ellipsis) { + if (block->style().textOverflow().isEllipsis()) { for (auto lineBox = InlineIterator::firstLineBoxFor(*block); lineBox; lineBox.traverseNext()) { if (lineBox->hasEllipsis()) { dir = block->writingMode().computedTextDirection(); diff --git a/Source/WebCore/rendering/TextAutoSizing.cpp b/Source/WebCore/rendering/TextAutoSizing.cpp index c0a4f871cb02..2715c5e9a753 100644 --- a/Source/WebCore/rendering/TextAutoSizing.cpp +++ b/Source/WebCore/rendering/TextAutoSizing.cpp @@ -130,7 +130,11 @@ unsigned TextAutoSizingHashTranslator::hash(const Style::ComputedStyle& style) hash ^= std::to_underlying(style.rtlOrdering()); hash ^= std::to_underlying(style.position()); hash ^= std::to_underlying(style.floating()); - hash ^= std::to_underlying(style.textOverflow()); + hash ^= style.textOverflow().switchOn( + [](const CSS::Keyword::Clip&) -> unsigned { return computeHash(0); }, + [](const CSS::Keyword::Ellipsis&) -> unsigned { return computeHash(1); }, + [](const Style::String& string) -> unsigned { return computeHash(2, string.value); } + ); return hash; } diff --git a/Source/WebCore/rendering/style/RenderStyleConstants.cpp b/Source/WebCore/rendering/style/RenderStyleConstants.cpp index 8f603c098df9..9b029bd807ef 100644 --- a/Source/WebCore/rendering/style/RenderStyleConstants.cpp +++ b/Source/WebCore/rendering/style/RenderStyleConstants.cpp @@ -1147,15 +1147,6 @@ TextStream& operator<<(TextStream& ts, TextJustify justify) return ts; } -TextStream& operator<<(TextStream& ts, TextOverflow overflow) -{ - switch (overflow) { - case TextOverflow::Clip: ts << "clip"_s; break; - case TextOverflow::Ellipsis: ts << "ellipsis"_s; break; - } - return ts; -} - TextStream& operator<<(TextStream& ts, TextSecurity textSecurity) { switch (textSecurity) { diff --git a/Source/WebCore/rendering/style/RenderStyleConstants.h b/Source/WebCore/rendering/style/RenderStyleConstants.h index cc09c50e3885..8f6ffbadfb23 100644 --- a/Source/WebCore/rendering/style/RenderStyleConstants.h +++ b/Source/WebCore/rendering/style/RenderStyleConstants.h @@ -812,11 +812,6 @@ enum class TextEmphasisMark : uint8_t { Sesame }; -enum class TextOverflow : bool { - Clip, - Ellipsis -}; - enum class TextWrapMode : bool { Wrap, NoWrap @@ -1247,7 +1242,6 @@ WTF::TextStream& operator<<(WTF::TextStream&, TextEmphasisFill); WTF::TextStream& operator<<(WTF::TextStream&, TextEmphasisMark); WTF::TextStream& operator<<(WTF::TextStream&, TextGroupAlign); WTF::TextStream& operator<<(WTF::TextStream&, TextJustify); -WTF::TextStream& operator<<(WTF::TextStream&, TextOverflow); WTF::TextStream& operator<<(WTF::TextStream&, TextSecurity); WTF::TextStream& operator<<(WTF::TextStream&, TextWrapMode); WTF::TextStream& operator<<(WTF::TextStream&, TextWrapStyle); diff --git a/Source/WebCore/style/computed/StyleComputedStyleBase.h b/Source/WebCore/style/computed/StyleComputedStyleBase.h index 2a0ff98d224e..e5d33bf577f1 100644 --- a/Source/WebCore/style/computed/StyleComputedStyleBase.h +++ b/Source/WebCore/style/computed/StyleComputedStyleBase.h @@ -166,7 +166,6 @@ enum class TextDecorationSkipInk : uint8_t; enum class TextDecorationStyle : uint8_t; enum class TextGroupAlign : uint8_t; enum class TextJustify : uint8_t; -enum class TextOverflow : bool; enum class TextRenderingMode : uint8_t; enum class TextSecurity : uint8_t; enum class TextTransform : uint8_t; @@ -364,6 +363,7 @@ struct TextDecorationThickness; struct TextEmphasisPosition; struct TextEmphasisStyle; struct TextIndent; +struct TextOverflow; struct TextShadow; struct TextSizeAdjust; struct TextSpacingTrim; diff --git a/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.cpp b/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.cpp index dcfb32dd42f7..5b2a8f228e87 100644 --- a/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.cpp +++ b/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.cpp @@ -60,11 +60,11 @@ NonInheritedMiscData::NonInheritedMiscData() , objectPosition(ComputedStyle::initialObjectPosition()) , objectViewBox(ComputedStyle::initialObjectViewBox()) , order(ComputedStyle::initialOrder()) + , textOverflow(ComputedStyle::initialTextOverflow()) , tableLayout(static_cast(ComputedStyle::initialTableLayout())) , appearance(static_cast(ComputedStyle::initialAppearance())) , usedAppearance(static_cast(ComputedStyle::initialAppearance())) , userSelect(static_cast(ComputedStyle::initialUserSelect())) - , textOverflow(static_cast(ComputedStyle::initialTextOverflow())) , userDrag(static_cast(ComputedStyle::initialUserDrag())) , objectFit(static_cast(ComputedStyle::initialObjectFit())) , resize(static_cast(ComputedStyle::initialResize())) @@ -95,6 +95,7 @@ NonInheritedMiscData::NonInheritedMiscData(const NonInheritedMiscData& o) , objectPosition(o.objectPosition) , objectViewBox(o.objectViewBox) , order(o.order) + , textOverflow(o.textOverflow) , hasAttrContent(o.hasAttrContent) , hasDisplayAffectedByAnimations(o.hasDisplayAffectedByAnimations) #if ENABLE(DARK_MODE_CSS) @@ -108,7 +109,6 @@ NonInheritedMiscData::NonInheritedMiscData(const NonInheritedMiscData& o) , appearance(o.appearance) , usedAppearance(o.usedAppearance) , userSelect(o.userSelect) - , textOverflow(o.textOverflow) , userDrag(o.userDrag) , objectFit(o.objectFit) , resize(o.resize) @@ -146,6 +146,7 @@ bool NonInheritedMiscData::operator==(const NonInheritedMiscData& o) const && objectPosition == o.objectPosition && objectViewBox == o.objectViewBox && order == o.order + && textOverflow == o.textOverflow && hasAttrContent == o.hasAttrContent && hasDisplayAffectedByAnimations == o.hasDisplayAffectedByAnimations #if ENABLE(DARK_MODE_CSS) @@ -159,7 +160,6 @@ bool NonInheritedMiscData::operator==(const NonInheritedMiscData& o) const && appearance == o.appearance && usedAppearance == o.usedAppearance && userSelect == o.userSelect - && textOverflow == o.textOverflow && userDrag == o.userDrag && objectFit == o.objectFit && resize == o.resize; @@ -202,6 +202,7 @@ void NonInheritedMiscData::dumpDifferences(TextStream& ts, const NonInheritedMis LOG_IF_DIFFERENT(objectPosition); LOG_IF_DIFFERENT(objectViewBox); LOG_IF_DIFFERENT(order); + LOG_IF_DIFFERENT(textOverflow); LOG_IF_DIFFERENT_WITH_CAST(bool, hasAttrContent); LOG_IF_DIFFERENT_WITH_CAST(bool, hasDisplayAffectedByAnimations); @@ -220,9 +221,6 @@ void NonInheritedMiscData::dumpDifferences(TextStream& ts, const NonInheritedMis LOG_IF_DIFFERENT_WITH_CAST(StyleAppearance, usedAppearance); LOG_IF_DIFFERENT_WITH_CAST(UserSelect, userSelect); - - LOG_IF_DIFFERENT_WITH_CAST(bool, textOverflow); - LOG_IF_DIFFERENT_WITH_CAST(UserDrag, userDrag); LOG_IF_DIFFERENT_WITH_CAST(ObjectFit, objectFit); LOG_IF_DIFFERENT_WITH_CAST(Resize, resize); diff --git a/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.h b/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.h index 7907e79d1e53..a66c0dbc915d 100644 --- a/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.h +++ b/Source/WebCore/style/computed/data/StyleNonInheritedMiscData.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -101,6 +102,7 @@ class NonInheritedMiscData : public RefCounted { ObjectPosition objectPosition; ObjectViewBox objectViewBox; Order order; + TextOverflow textOverflow; PREFERRED_TYPE(bool) unsigned hasAttrContent : 1 { false }; PREFERRED_TYPE(bool) unsigned hasDisplayAffectedByAnimations : 1 { false }; @@ -115,7 +117,6 @@ class NonInheritedMiscData : public RefCounted { PREFERRED_TYPE(StyleAppearance) unsigned appearance : appearanceBitWidth; PREFERRED_TYPE(StyleAppearance) unsigned usedAppearance : appearanceBitWidth; PREFERRED_TYPE(UserSelect) unsigned userSelect : 2; - PREFERRED_TYPE(bool) unsigned textOverflow : 1; // Whether or not lines that spill out should be truncated with "..." PREFERRED_TYPE(UserDrag) unsigned userDrag : 2; PREFERRED_TYPE(ObjectFit) unsigned objectFit : 3; PREFERRED_TYPE(Resize) unsigned resize : 3; diff --git a/Source/WebCore/style/values/overflow/StyleTextOverflow.cpp b/Source/WebCore/style/values/overflow/StyleTextOverflow.cpp new file mode 100644 index 000000000000..9a9a9d545a96 --- /dev/null +++ b/Source/WebCore/style/values/overflow/StyleTextOverflow.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2026 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 "StyleTextOverflow.h" + +#include "CSSKeywordValue.h" +#include "StyleBuilderChecking.h" + +namespace WebCore { +namespace Style { + +// MARK: - Conversion + +auto CSSValueConversion::operator()(BuilderState& state, const CSSValue& value) -> TextOverflow +{ + if (auto* keywordValue = dynamicDowncast(value)) { + switch (keywordValue->valueID()) { + case CSSValueClip: + return CSS::Keyword::Clip { }; + case CSSValueEllipsis: + return CSS::Keyword::Ellipsis { }; + default: + state.setCurrentPropertyInvalidAtComputedValueTime(); + return CSS::Keyword::Clip { }; + } + } + + return toStyleFromCSSValue(state, value); +} + +} // namespace Style +} // namespace WebCore diff --git a/Source/WebCore/style/values/overflow/StyleTextOverflow.h b/Source/WebCore/style/values/overflow/StyleTextOverflow.h new file mode 100644 index 000000000000..f7fc9376c7b0 --- /dev/null +++ b/Source/WebCore/style/values/overflow/StyleTextOverflow.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +namespace WebCore { +namespace Style { + +// <'text-overflow'> = clip | ellipsis | +// https://drafts.csswg.org/css-overflow-3/#propdef-text-overflow +struct TextOverflow { + TextOverflow(CSS::Keyword::Clip) + { + } + + TextOverflow(CSS::Keyword::Ellipsis) + : m_type { Type::Ellipsis } + { + } + + TextOverflow(String&& string) + : m_type { Type::String } + , m_string { WTF::move(string.value) } + { + } + + bool isClip() const { return m_type == Type::Clip; } + bool isEllipsis() const { return m_type == Type::Ellipsis; } + bool isString() const { return m_type == Type::String; } + + template decltype(auto) switchOn(F&&... f) const + { + auto visitor = WTF::makeVisitor(std::forward(f)...); + + switch (m_type) { + case Type::Clip: + return visitor(CSS::Keyword::Clip { }); + case Type::Ellipsis: + return visitor(CSS::Keyword::Ellipsis { }); + case Type::String: + return visitor(String { m_string }); + } + RELEASE_ASSERT_NOT_REACHED(); + } + + bool operator==(const TextOverflow&) const = default; + +private: + enum class Type : uint8_t { Clip, Ellipsis, String }; + + Type m_type { Type::Clip }; + AtomString m_string { nullAtom() }; +}; + +// MARK: - Conversion + +template<> struct CSSValueConversion { + auto operator()(BuilderState&, const CSSValue&) -> TextOverflow; +}; + +} // namespace Style +} // namespace WebCore + +DEFINE_VARIANT_LIKE_CONFORMANCE(WebCore::Style::TextOverflow) diff --git a/Source/WebCore/style/values/primitives/StyleKeyword+Mappings.h b/Source/WebCore/style/values/primitives/StyleKeyword+Mappings.h index cc699cfa51e4..bdb3ac131728 100644 --- a/Source/WebCore/style/values/primitives/StyleKeyword+Mappings.h +++ b/Source/WebCore/style/values/primitives/StyleKeyword+Mappings.h @@ -1438,12 +1438,6 @@ DEFINE_TO_FROM_CSS_VALUE_ID_FUNCTIONS #undef TYPE #undef FOR_EACH -#define TYPE TextOverflow -#define FOR_EACH(CASE) CASE(Clip) CASE(Ellipsis) -DEFINE_TO_FROM_CSS_VALUE_ID_FUNCTIONS -#undef TYPE -#undef FOR_EACH - constexpr CSSValueID toCSSValueID(TextWrapMode wrap) { switch (wrap) { From 881e23eaa339d1f7ef94865b7bed631e38a4a7b4 Mon Sep 17 00:00:00 2001 From: WebKit Revert Bot Date: Fri, 28 Aug 2026 00:22:41 -0700 Subject: [PATCH 016/103] Unreviewed, reverting 319908@main (80fc868e7373) https://bugs.webkit.org/show_bug.cgi?id=322824 Another speculative revert for JetStream3 ~1% regression Reverted change: [JSC] add/sub of a zero immediate should be a mov https://bugs.webkit.org/show_bug.cgi?id=322504 319908@main (80fc868e7373) Canonical link: https://commits.webkit.org/320024@main --- .../assembler/MacroAssemblerARM64.h | 24 -- .../assembler/MacroAssemblerX86_64.h | 32 -- Source/JavaScriptCore/assembler/testmasm.cpp | 337 ++++++------------ 3 files changed, 112 insertions(+), 281 deletions(-) diff --git a/Source/JavaScriptCore/assembler/MacroAssemblerARM64.h b/Source/JavaScriptCore/assembler/MacroAssemblerARM64.h index 755f3c126859..7a2962e3c8f1 100644 --- a/Source/JavaScriptCore/assembler/MacroAssemblerARM64.h +++ b/Source/JavaScriptCore/assembler/MacroAssemblerARM64.h @@ -191,10 +191,6 @@ class MacroAssemblerARM64 : public AbstractMacroAssembler { void add32(TrustedImm32 imm, RegisterID src, RegisterID dest) { auto immediate = imm.m_value; - if (!immediate) { - zeroExtend32ToWord(src, dest); - return; - } if (auto tuple = tryExtractShiftedImm(immediate)) { auto [u12, shift, inverted] = tuple.value(); if (!inverted) @@ -326,10 +322,6 @@ class MacroAssemblerARM64 : public AbstractMacroAssembler { void add64(TrustedImm32 imm, RegisterID src, RegisterID dest) { auto immediate = imm.m_value; - if (!immediate) { - move(src, dest); - return; - } if (auto tuple = tryExtractShiftedImm(immediate)) { auto [u12, shift, inverted] = tuple.value(); if (!inverted) @@ -360,10 +352,6 @@ class MacroAssemblerARM64 : public AbstractMacroAssembler { void add64(TrustedImm64 imm, RegisterID src, RegisterID dest) { auto immediate = imm.m_value; - if (!immediate) { - move(src, dest); - return; - } if (auto tuple = tryExtractShiftedImm(immediate)) { auto [u12, shift, inverted] = tuple.value(); if (!inverted) @@ -1515,10 +1503,6 @@ class MacroAssemblerARM64 : public AbstractMacroAssembler { void sub32(RegisterID left, TrustedImm32 imm, RegisterID dest) { auto immediate = imm.m_value; - if (!immediate) { - zeroExtend32ToWord(left, dest); - return; - } if (auto tuple = tryExtractShiftedImm(immediate)) { auto [u12, shift, inverted] = tuple.value(); if (!inverted) @@ -1591,10 +1575,6 @@ class MacroAssemblerARM64 : public AbstractMacroAssembler { void sub64(RegisterID left, TrustedImm32 imm, RegisterID dest) { auto immediate = imm.m_value; - if (!immediate) { - move(left, dest); - return; - } if (auto tuple = tryExtractShiftedImm(immediate)) { auto [u12, shift, inverted] = tuple.value(); if (!inverted) @@ -1615,10 +1595,6 @@ class MacroAssemblerARM64 : public AbstractMacroAssembler { void sub64(RegisterID left, TrustedImm64 imm, RegisterID dest) { auto immediate = imm.m_value; - if (!immediate) { - move(left, dest); - return; - } if (auto tuple = tryExtractShiftedImm(immediate)) { auto [u12, shift, inverted] = tuple.value(); if (!inverted) diff --git a/Source/JavaScriptCore/assembler/MacroAssemblerX86_64.h b/Source/JavaScriptCore/assembler/MacroAssemblerX86_64.h index b996967607a9..30180cf1681e 100644 --- a/Source/JavaScriptCore/assembler/MacroAssemblerX86_64.h +++ b/Source/JavaScriptCore/assembler/MacroAssemblerX86_64.h @@ -5122,9 +5122,6 @@ class MacroAssemblerX86_64 : public AbstractMacroAssembler { void add64(TrustedImm32 imm, RegisterID srcDest) { - if (!imm.m_value) - return; - if (imm.m_value == 1) m_assembler.incq_r(srcDest); else @@ -5133,9 +5130,6 @@ class MacroAssemblerX86_64 : public AbstractMacroAssembler { void add64(TrustedImm64 imm, RegisterID dest) { - if (!imm.m_value) - return; - if (imm.m_value == 1) m_assembler.incq_r(dest); else { @@ -5146,21 +5140,11 @@ class MacroAssemblerX86_64 : public AbstractMacroAssembler { void add64(TrustedImm32 imm, RegisterID src, RegisterID dest) { - if (!imm.m_value) { - move(src, dest); - return; - } - m_assembler.leaq_mr(imm.m_value, src, dest); } void add64(TrustedImm64 imm, RegisterID src, RegisterID dest) { - if (!imm.m_value) { - move(src, dest); - return; - } - if (WTF::isRepresentableAs(imm.m_value)) m_assembler.leaq_mr(imm.m_value, src, dest); else { @@ -5801,9 +5785,6 @@ class MacroAssemblerX86_64 : public AbstractMacroAssembler { void sub64(TrustedImm32 imm, RegisterID dest) { - if (!imm.m_value) - return; - if (imm.m_value == 1) m_assembler.decq_r(dest); else @@ -5812,11 +5793,6 @@ class MacroAssemblerX86_64 : public AbstractMacroAssembler { void sub64(RegisterID a, TrustedImm32 imm, RegisterID dest) { - if (!imm.m_value) { - move(a, dest); - return; - } - if (a == dest) { sub64(imm, dest); return; @@ -5831,9 +5807,6 @@ class MacroAssemblerX86_64 : public AbstractMacroAssembler { void sub64(TrustedImm64 imm, RegisterID dest) { - if (!imm.m_value) - return; - if (imm.m_value == 1) m_assembler.decq_r(dest); else { @@ -5844,11 +5817,6 @@ class MacroAssemblerX86_64 : public AbstractMacroAssembler { void sub64(RegisterID src, TrustedImm64 imm, RegisterID dest) { - if (!imm.m_value) { - move(src, dest); - return; - } - if (src == dest) { sub64(imm, dest); return; diff --git a/Source/JavaScriptCore/assembler/testmasm.cpp b/Source/JavaScriptCore/assembler/testmasm.cpp index 3f495628f492..4c586c98d871 100644 --- a/Source/JavaScriptCore/assembler/testmasm.cpp +++ b/Source/JavaScriptCore/assembler/testmasm.cpp @@ -975,216 +975,6 @@ void testStore64Imm64AddressPointer() doTest(0xAAAA432198765555); } -void testAdd32Imm() -{ - for (auto immediate : int32Operands()) { - for (auto immediate2 : int32Operands()) { - auto add = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.move(CCallHelpers::TrustedImm32(immediate), GPRInfo::returnValueGPR); - jit.add32(CCallHelpers::TrustedImm32(immediate2), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - CHECK_EQ(invoke(add), static_cast(immediate) + static_cast(immediate2)); - } - } -} - -void testAdd32ArgImm() -{ - for (auto immediate : int32Operands()) { - auto add = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.add32(CCallHelpers::TrustedImm32(immediate), GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - - for (auto value : int32Operands()) - CHECK_EQ(invoke(add, value), static_cast(value) + static_cast(immediate)); - } -} - -void testAdd64Imm32() -{ - for (auto immediate : int64Operands()) { - for (auto immediate2 : int32Operands()) { - auto add = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.move(CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); - jit.add64(CCallHelpers::TrustedImm32(immediate2), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - CHECK_EQ(invoke(add), static_cast(immediate) + static_cast(immediate2)); - } - } -} - -void testAdd64ArgImm32() -{ - for (auto immediate : int32Operands()) { - auto add = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.add64(CCallHelpers::TrustedImm32(immediate), GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - - for (auto value : int64Operands()) - CHECK_EQ(invoke(add, value), static_cast(value) + static_cast(immediate)); - } -} - -void testAdd64Imm64() -{ - for (auto immediate : int64Operands()) { - for (auto immediate2 : int64Operands()) { - auto add = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.move(CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); - jit.add64(CCallHelpers::TrustedImm64(immediate2), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - CHECK_EQ(invoke(add), static_cast(immediate) + static_cast(immediate2)); - } - } -} - -void testAdd64ArgImm64() -{ - for (auto immediate : int64Operands()) { - auto add = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.add64(CCallHelpers::TrustedImm64(immediate), GPRInfo::argumentGPR0, GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - - for (auto value : int64Operands()) - CHECK_EQ(invoke(add, value), static_cast(value) + static_cast(immediate)); - } -} - -void testSub32Args() -{ - for (auto value : int32Operands()) { - auto sub = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.sub32(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1, GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - - for (auto value2 : int32Operands()) - CHECK_EQ(invoke(sub, value, value2), static_cast(value - value2)); - } -} - -void testSub32Imm() -{ - for (auto immediate : int32Operands()) { - for (auto immediate2 : int32Operands()) { - auto sub = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.move(CCallHelpers::TrustedImm32(immediate), GPRInfo::returnValueGPR); - jit.sub32(CCallHelpers::TrustedImm32(immediate2), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - CHECK_EQ(invoke(sub), static_cast(immediate - immediate2)); - } - } -} - -void testSub64Imm32() -{ - for (auto immediate : int64Operands()) { - for (auto immediate2 : int32Operands()) { - auto sub = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.move(CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); - jit.sub64(CCallHelpers::TrustedImm32(immediate2), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - CHECK_EQ(invoke(sub), static_cast(immediate - immediate2)); - } - } -} - -void testSub64ArgImm32() -{ - for (auto immediate : int32Operands()) { - auto sub = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.sub64(GPRInfo::argumentGPR0, CCallHelpers::TrustedImm32(immediate), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - - for (auto value : int64Operands()) - CHECK_EQ(invoke(sub, value), static_cast(value - immediate)); - } -} - -void testSub64Imm64() -{ - for (auto immediate : int64Operands()) { - for (auto immediate2 : int64Operands()) { - auto sub = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.move(CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); - jit.sub64(CCallHelpers::TrustedImm64(immediate2), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - CHECK_EQ(invoke(sub), static_cast(immediate - immediate2)); - } - } -} - -void testSub64ArgImm64() -{ - for (auto immediate : int64Operands()) { - auto sub = compile([=] (CCallHelpers& jit) { - emitFunctionPrologue(jit); - - jit.sub64(GPRInfo::argumentGPR0, CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); - - emitFunctionEpilogue(jit); - jit.ret(); - }); - - for (auto value : int64Operands()) - CHECK_EQ(invoke(sub, value), static_cast(value - immediate)); - } -} - #endif // CPU(X86_64) || CPU(ARM64) void testCompareDouble(MacroAssembler::DoubleCondition condition) @@ -1435,6 +1225,111 @@ void testMultiplyAddZeroExtend32() } } +void testSub32Args() +{ + for (auto value : int32Operands()) { + auto sub = compile([=] (CCallHelpers& jit) { + emitFunctionPrologue(jit); + + jit.sub32(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1, GPRInfo::returnValueGPR); + + emitFunctionEpilogue(jit); + jit.ret(); + }); + + for (auto value2 : int32Operands()) + CHECK_EQ(invoke(sub, value, value2), static_cast(value - value2)); + } +} + +void testSub32Imm() +{ + for (auto immediate : int32Operands()) { + for (auto immediate2 : int32Operands()) { + auto sub = compile([=] (CCallHelpers& jit) { + emitFunctionPrologue(jit); + + jit.move(CCallHelpers::TrustedImm32(immediate), GPRInfo::returnValueGPR); + jit.sub32(CCallHelpers::TrustedImm32(immediate2), GPRInfo::returnValueGPR); + + emitFunctionEpilogue(jit); + jit.ret(); + }); + CHECK_EQ(invoke(sub), static_cast(immediate - immediate2)); + } + } +} + +void testSub64Imm32() +{ + for (auto immediate : int64Operands()) { + for (auto immediate2 : int32Operands()) { + auto sub = compile([=] (CCallHelpers& jit) { + emitFunctionPrologue(jit); + + jit.move(CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); + jit.sub64(CCallHelpers::TrustedImm32(immediate2), GPRInfo::returnValueGPR); + + emitFunctionEpilogue(jit); + jit.ret(); + }); + CHECK_EQ(invoke(sub), static_cast(immediate - immediate2)); + } + } +} + +void testSub64ArgImm32() +{ + for (auto immediate : int32Operands()) { + auto sub = compile([=] (CCallHelpers& jit) { + emitFunctionPrologue(jit); + + jit.sub64(GPRInfo::argumentGPR0, CCallHelpers::TrustedImm32(immediate), GPRInfo::returnValueGPR); + + emitFunctionEpilogue(jit); + jit.ret(); + }); + + for (auto value : int64Operands()) + CHECK_EQ(invoke(sub, value), static_cast(value - immediate)); + } +} + +void testSub64Imm64() +{ + for (auto immediate : int64Operands()) { + for (auto immediate2 : int64Operands()) { + auto sub = compile([=] (CCallHelpers& jit) { + emitFunctionPrologue(jit); + + jit.move(CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); + jit.sub64(CCallHelpers::TrustedImm64(immediate2), GPRInfo::returnValueGPR); + + emitFunctionEpilogue(jit); + jit.ret(); + }); + CHECK_EQ(invoke(sub), static_cast(immediate - immediate2)); + } + } +} + +void testSub64ArgImm64() +{ + for (auto immediate : int64Operands()) { + auto sub = compile([=] (CCallHelpers& jit) { + emitFunctionPrologue(jit); + + jit.sub64(GPRInfo::argumentGPR0, CCallHelpers::TrustedImm64(immediate), GPRInfo::returnValueGPR); + + emitFunctionEpilogue(jit); + jit.ret(); + }); + + for (auto value : int64Operands()) + CHECK_EQ(invoke(sub, value), static_cast(value - immediate)); + } +} + void testMultiplySubSignExtend32() { // d = a - SExt32(n) * SExt32(m) @@ -8530,21 +8425,6 @@ void run(const char* filter) WTF_IGNORES_THREAD_SAFETY_ANALYSIS RUN(testCountTrailingZeros64WithoutNullCheck()); RUN(testShiftAndAdd()); RUN(testStore64Imm64AddressPointer()); - - RUN(testAdd32Imm()); - RUN(testAdd32ArgImm()); - RUN(testAdd64Imm32()); - RUN(testAdd64ArgImm32()); - RUN(testAdd64Imm64()); - RUN(testAdd64ArgImm64()); - - RUN(testSub32Args()); - RUN(testSub32Imm()); - RUN(testSub64Imm32()); - RUN(testSub64ArgImm32()); - RUN(testSub64Imm64()); - RUN(testSub64ArgImm64()); - #endif RUN(testLoadAcq8SignedExtendTo32_Address_RegisterID()); @@ -8580,6 +8460,13 @@ void run(const char* filter) WTF_IGNORES_THREAD_SAFETY_ANALYSIS RUN(testMultiplySignExtend32()); RUN(testMultiplyZeroExtend32()); + RUN(testSub32Args()); + RUN(testSub32Imm()); + RUN(testSub64Imm32()); + RUN(testSub64ArgImm32()); + RUN(testSub64Imm64()); + RUN(testSub64ArgImm64()); + RUN(testMultiplyAddSignExtend32()); RUN(testMultiplyAddZeroExtend32()); RUN(testMultiplySubSignExtend32()); From 96b67294979146c46c763f8c41a15883f5384f79 Mon Sep 17 00:00:00 2001 From: Mark Lam Date: Fri, 28 Aug 2026 00:51:19 -0700 Subject: [PATCH 017/103] [Re-landing] Introducing Mya, a MemorY Analyzer, and libJavaScriptCoreTools. https://bugs.webkit.org/show_bug.cgi?id=321334 rdar://184365506 Reviewed by Elliott Williams and Dan Hecht. Mya (pronounced like Maya) is a new command line tool for examining the JSC and WebKit memory use in a running process. It attaches to a target by PID and captures snapshots of its memory (read-only Mach corpses of the process), and then enables queries on these snapshots via a REPL. Here is an example user work flow: # mya --help // Prints help text and exit. # mya --pid 12345 // Attach to the process, and take a snapshot. Attached to 12345 Captured Snapshot #1 of 12345 >>> snapshot list // Lists all existing snapshots. #1: pid 12345 ... // Only one so far. >>> snapshot // Take another snapshot, and switch to it. Captured Snapshot #2 of 12345 // Capture and switch to snapshot 2. >>> status // Shows current pid and snapshot being examined. Using snapshot 2 of pid 12345 // The snapshot command above switched us. >>> snapshot --pid 23456 Attached to 23456 // Attach and switch to this second process. Captured Snapshot #3 of 23456 // Capture and switch to snapshot 3. >>> snapshot list // Lists all existing snapshots. #1: pid 12345 ... #2: pid 12345 ... #3: pid 23456 ... >>> status // Shows current pid and snapshot being examined. Using snapshot 2 of pid 23456 // Note: the snapshot command above switched us. >>> detach // Detach from the current process. Detached from 23456 >>> snapshot // Fails. No attached process. Unable to capture snapshot. Not attached to any process. Use `attach` command or specify `--pid` argument for the snapshot command. >> snapshot info 1 // Show info on snapshot 1. ... >>> snapshot delete 2 // Delete snapshot 2. ... >>> snapshot diff 1 3 // Diff between snapshots 1 and 3. Not yet implemented. ... >> snapshot 1 // Select snapshot 1 for analysis. >>> thread list // Prints info on all thread in the current snapshot. Threads in snapshot #1 (pid 12345): INDEX TID STATE USER(ms) SYS(ms) SP STACK SIZE PAGES RESIDENT DIRTY NAME 1 0x12278c6 halted 23645.003 2937.065 0x16b7361e0 0x16af3c000-0x16b738000 7.98 MB 511 7 7 - 2 0x12278c7 halted 56.147 23.816 0x167386aa0 0x167304000-0x16738c000 544 KB 34 1 1 WebCore: ServiceWorker 3 0x12278c8 halted 0.211 0.472 0x105986b30 0x105904000-0x10598c000 544 KB 34 1 1 - >>> p/x &g_config // Prints the address of the g_config record. &g_config = 0x1f48e0000 The corpse management and analysis machinery lives in libJavaScriptCoreTools, a new static library built alongside the JavaScriptCore framework. Clients that need to do this type of snapshot analysis can link against it. By design, the corpse management and analysis code is not linked and packaged with the JavaScriptCore framework by default because they are not needed there. libJavaScriptCoreTools provides abstractions like Address, Process, Snapshot, Thread, Region, Symbol, etc. for inspecting and analyzing such WebKit and JSC corpses. Corpse Addresses are numbered based on the target process' address space mapping, not the client tool like mya. Currently, libJavaScriptCoreTools copies over memory from the corpse for inspection as an initial bootstrap implementation. In subsequent patches, we will introduce a Memory Manager that will make reading corpse memory more efficient. Symbol lookup resolves a name to an address by walking the dyld exports trie of each image loaded in the corpse. That needs no cooperation from the target and no debug information, but it only finds exported symbols. Everything read out of a corpse is untrusted input: a corrupted target could otherwise steer mya into unbounded work or bad reads. The parsing paths therefore bound the work they will do and reject implausible sizes, counts, and offsets rather than trusting what the target claims. mya and libJavaScriptCoreTools are only for Apple platforms, since they are built on Mach task APIs, which are only available on Apple platforms. Also, it is not a goal to support analysis of Rosetta processes. Some subset of functionality may still work, but only on a WYSIWYG basis. Tests: Source/JavaScriptCore/corpse/tests/ testLibJSCTools is a new test tool for libJavaScriptCoreTools, built with the other JSC test tools and run by: run-javascriptcore-tests --testlibjsctools A task may take a corpse of itself with no entitlement and no privilege, and the tests are built on that: they snapshot the running test process and check what the corpse reports against what that process already knows about itself. A symbol resolved out of the corpse, for instance, has to land on the address this process uses for it. The suites are: ByteParser ULEB128 and C string decoding, including the truncated and out of range encodings that untrusted data can hold. ExportsTrie terminal and edge decoding for every export kind and flag, the malformed tries that a corrupt corpse can present, and that a cyclic trie still terminates. Also fuzzed from a fixed seed on every run, under a watchdog, because the decoder's contract is to bound its work on any input at all. Address null, ordering, arithmetic, and the ptrauth and top-byte stripping that a pointer out of a corpse needs. Process attach, detach, re-attach, a pid that has exited, and a target running under Rosetta translation. Snapshot validity, identifier assignment, and that repeatedly snapshotting leaves no Mach port behind. Region a mapping of known size and residency, an unmapped hole, and an address inside the shared cache submap. Thread thread names and their truncation, and that a thread's stack pointer lies inside the stack region reported for it. Symbol g_config, malloc and environ resolved out of a corpse of this process, each compared against the address it has locally, plus the names that are deliberately not found: a symbol hidden from the linker, and a name given with its underscore already attached. For the build, libJavaScriptCoreTools, mya, and testLibJSCTools are deliberately put in jsc's dependency closure in the JavaScriptCore_executables XBS project. This is so that they can piggy-back off of jsc shell's build phase i.e. only trigger a build after JavaScriptCore.framework is done building, and trigger an install like the jsc shell is (albeit to their respective install destinations, which may defer). Update: fixes simulators and MacCatalyst builds to actually not install mya, and not build libJavaScriptCore. A stubbed version of testLibJSCTools is still being build and installed. This is to keep run-javascriptcore-tests from failing due to a missing testLibJSCTools executable. * Source/JavaScriptCore/CMakeLists.txt: * Source/JavaScriptCore/Configurations/Mya.xcconfig: Added. * Source/JavaScriptCore/Configurations/TestLibJSCTools.xcconfig: Added. * Source/JavaScriptCore/Configurations/libJavaScriptCoreTools.xcconfig: Added. * Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj: * Source/JavaScriptCore/Scripts/process-entitlements.sh: * Source/JavaScriptCore/corpse/CMakeLists.txt: Added. * Source/JavaScriptCore/corpse/CorpseAddress.h: Added. * Source/JavaScriptCore/corpse/CorpseByteParser.cpp: Added. * Source/JavaScriptCore/corpse/CorpseByteParser.h: Added. * Source/JavaScriptCore/corpse/CorpseClient.cpp: Added. * Source/JavaScriptCore/corpse/CorpseClient.h: Added. * Source/JavaScriptCore/corpse/CorpseError.cpp: Added. * Source/JavaScriptCore/corpse/CorpseError.h: Added. * Source/JavaScriptCore/corpse/CorpseExportsTrie.cpp: Added. * Source/JavaScriptCore/corpse/CorpseExportsTrie.h: Added. * Source/JavaScriptCore/corpse/CorpseProcess.cpp: Added. * Source/JavaScriptCore/corpse/CorpseProcess.h: Added. * Source/JavaScriptCore/corpse/CorpseRegion.cpp: Added. * Source/JavaScriptCore/corpse/CorpseRegion.h: Added. * Source/JavaScriptCore/corpse/CorpseSnapshot.cpp: Added. * Source/JavaScriptCore/corpse/CorpseSnapshot.h: Added. * Source/JavaScriptCore/corpse/CorpseSymbol.cpp: Added. * Source/JavaScriptCore/corpse/CorpseSymbol.h: Added. * Source/JavaScriptCore/corpse/CorpseThread.cpp: Added. * Source/JavaScriptCore/corpse/CorpseThread.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseAddressTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseAddressTest.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseProcessTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseProcessTest.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseRegionTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseRegionTest.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.h: Added. * Source/JavaScriptCore/corpse/tests/CorpseThreadTest.cpp: Added. * Source/JavaScriptCore/corpse/tests/CorpseThreadTest.h: Added. * Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.cpp: Added. * Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.h: Added. * Source/JavaScriptCore/corpse/tests/testLibJSCTools.cpp: Added. * Source/JavaScriptCore/mya/mya.cpp: Added. * Source/JavaScriptCore/shell/CMakeLists.txt: * Source/JavaScriptCore/shell/PlatformCocoa.cmake: * Tools/CISupport/ews-build/steps.py: * Tools/Scripts/run-javascriptcore-tests: * Tools/Scripts/webkitperl/BuildSubproject.pm: * Tools/Scripts/webkitpy/common/config/ports.py: * Tools/Scripts/webkitpy/common/config/ports_unittest.py: Canonical link: https://commits.webkit.org/320025@main --- Source/JavaScriptCore/CMakeLists.txt | 28 + .../Configurations/Mya.xcconfig | 43 + .../Configurations/TestLibJSCTools.xcconfig | 35 + .../libJavaScriptCoreTools.xcconfig | 54 + .../JavaScriptCore.xcodeproj/project.pbxproj | 578 +++++++ .../Scripts/process-entitlements.sh | 59 + Source/JavaScriptCore/corpse/CMakeLists.txt | 32 + Source/JavaScriptCore/corpse/CorpseAddress.h | 100 ++ .../corpse/CorpseByteParser.cpp | 71 + .../JavaScriptCore/corpse/CorpseByteParser.h | 71 + Source/JavaScriptCore/corpse/CorpseClient.cpp | 45 + Source/JavaScriptCore/corpse/CorpseClient.h | 51 + Source/JavaScriptCore/corpse/CorpseError.cpp | 58 + Source/JavaScriptCore/corpse/CorpseError.h | 45 + .../corpse/CorpseExportsTrie.cpp | 144 ++ .../JavaScriptCore/corpse/CorpseExportsTrie.h | 69 + .../JavaScriptCore/corpse/CorpseProcess.cpp | 110 ++ Source/JavaScriptCore/corpse/CorpseProcess.h | 78 + Source/JavaScriptCore/corpse/CorpseRegion.cpp | 75 + Source/JavaScriptCore/corpse/CorpseRegion.h | 63 + .../JavaScriptCore/corpse/CorpseSnapshot.cpp | 98 ++ Source/JavaScriptCore/corpse/CorpseSnapshot.h | 101 ++ Source/JavaScriptCore/corpse/CorpseSymbol.cpp | 481 ++++++ Source/JavaScriptCore/corpse/CorpseSymbol.h | 113 ++ Source/JavaScriptCore/corpse/CorpseThread.cpp | 189 +++ Source/JavaScriptCore/corpse/CorpseThread.h | 82 + .../corpse/tests/CorpseAddressTest.cpp | 125 ++ .../corpse/tests/CorpseAddressTest.h | 36 + .../corpse/tests/CorpseByteParserTest.cpp | 186 +++ .../corpse/tests/CorpseByteParserTest.h | 36 + .../corpse/tests/CorpseExportsTrieTest.cpp | 811 ++++++++++ .../corpse/tests/CorpseExportsTrieTest.h | 43 + .../corpse/tests/CorpseProcessTest.cpp | 197 +++ .../corpse/tests/CorpseProcessTest.h | 36 + .../corpse/tests/CorpseRegionTest.cpp | 180 +++ .../corpse/tests/CorpseRegionTest.h | 39 + .../corpse/tests/CorpseSnapshotTest.cpp | 115 ++ .../corpse/tests/CorpseSnapshotTest.h | 39 + .../corpse/tests/CorpseSymbolTest.cpp | 161 ++ .../corpse/tests/CorpseSymbolTest.h | 36 + .../corpse/tests/CorpseThreadTest.cpp | 117 ++ .../corpse/tests/CorpseThreadTest.h | 39 + .../corpse/tests/LibJSCToolsTestUtilities.cpp | 205 +++ .../corpse/tests/LibJSCToolsTestUtilities.h | 159 ++ .../corpse/tests/testLibJSCTools.cpp | 155 ++ Source/JavaScriptCore/mya/mya.cpp | 1407 +++++++++++++++++ Source/JavaScriptCore/shell/CMakeLists.txt | 61 + .../JavaScriptCore/shell/PlatformCocoa.cmake | 2 + Tools/CISupport/ews-build/steps.py | 2 + Tools/Scripts/run-javascriptcore-tests | 25 + Tools/Scripts/webkitperl/BuildSubproject.pm | 4 +- Tools/Scripts/webkitpy/common/config/ports.py | 1 + .../webkitpy/common/config/ports_unittest.py | 2 +- 53 files changed, 7090 insertions(+), 2 deletions(-) create mode 100644 Source/JavaScriptCore/Configurations/Mya.xcconfig create mode 100644 Source/JavaScriptCore/Configurations/TestLibJSCTools.xcconfig create mode 100644 Source/JavaScriptCore/Configurations/libJavaScriptCoreTools.xcconfig create mode 100644 Source/JavaScriptCore/corpse/CMakeLists.txt create mode 100644 Source/JavaScriptCore/corpse/CorpseAddress.h create mode 100644 Source/JavaScriptCore/corpse/CorpseByteParser.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseByteParser.h create mode 100644 Source/JavaScriptCore/corpse/CorpseClient.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseClient.h create mode 100644 Source/JavaScriptCore/corpse/CorpseError.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseError.h create mode 100644 Source/JavaScriptCore/corpse/CorpseExportsTrie.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseExportsTrie.h create mode 100644 Source/JavaScriptCore/corpse/CorpseProcess.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseProcess.h create mode 100644 Source/JavaScriptCore/corpse/CorpseRegion.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseRegion.h create mode 100644 Source/JavaScriptCore/corpse/CorpseSnapshot.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseSnapshot.h create mode 100644 Source/JavaScriptCore/corpse/CorpseSymbol.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseSymbol.h create mode 100644 Source/JavaScriptCore/corpse/CorpseThread.cpp create mode 100644 Source/JavaScriptCore/corpse/CorpseThread.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseAddressTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseAddressTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseProcessTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseProcessTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseRegionTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseRegionTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseThreadTest.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/CorpseThreadTest.h create mode 100644 Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.cpp create mode 100644 Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.h create mode 100644 Source/JavaScriptCore/corpse/tests/testLibJSCTools.cpp create mode 100644 Source/JavaScriptCore/mya/mya.cpp diff --git a/Source/JavaScriptCore/CMakeLists.txt b/Source/JavaScriptCore/CMakeLists.txt index 87ea1b8e7ac2..a0a60ffce7a9 100644 --- a/Source/JavaScriptCore/CMakeLists.txt +++ b/Source/JavaScriptCore/CMakeLists.txt @@ -1617,6 +1617,26 @@ if (USE_INSPECTOR_SOCKET_SERVER) ) endif () +# The corpse memory-analysis support is built on Mach task and corpse APIs, so +# its headers have no content off Darwin and there is nothing to build or export. +if (APPLE) + list(APPEND JavaScriptCore_PRIVATE_INCLUDE_DIRECTORIES + "${JAVASCRIPTCORE_DIR}/corpse" + ) + list(APPEND JavaScriptCore_PRIVATE_FRAMEWORK_HEADERS + corpse/CorpseAddress.h + corpse/CorpseByteParser.h + corpse/CorpseClient.h + corpse/CorpseError.h + corpse/CorpseExportsTrie.h + corpse/CorpseProcess.h + corpse/CorpseRegion.h + corpse/CorpseSnapshot.h + corpse/CorpseSymbol.h + corpse/CorpseThread.h + ) +endif () + # GENERATOR 1-B: particular LUT creator (for 1 file only) GENERATE_HASH_LUT(${CMAKE_CURRENT_SOURCE_DIR}/parser/Keywords.table ${JavaScriptCore_DERIVED_SOURCES_DIR}/Lexer.lut.h) @@ -1870,6 +1890,14 @@ add_custom_target(JavaScriptCoreSharedScripts DEPENDS ${JavaScriptCore_SCRIPTS}) add_dependencies(JavaScriptCore JavaScriptCoreSharedScripts ${JavaScriptCore_EXTRA_DEPENDENCIES}) add_dependencies(JavaScriptCoreSharedScripts JSCBuiltins) +# The corpse code is part of JavaScriptCoreTools, which ships as a static library used +# only by tools for assisting in the development of JavaScriptCore and WebKit. This code +# is not needed for JavaScriptCore and WebKit functionality as a browser engine. The +# corpse code rely on Mach APIs which are only available on Apple platforms. +if (APPLE) + add_subdirectory(corpse) +endif () + if (ENABLE_JAVASCRIPT_SHELL) add_subdirectory(shell) endif () diff --git a/Source/JavaScriptCore/Configurations/Mya.xcconfig b/Source/JavaScriptCore/Configurations/Mya.xcconfig new file mode 100644 index 000000000000..32bdfefbe5a7 --- /dev/null +++ b/Source/JavaScriptCore/Configurations/Mya.xcconfig @@ -0,0 +1,43 @@ +// Copyright (C) 2026 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 "JSC.xcconfig" + +PRODUCT_NAME = mya; + +INSTALL_PATH = /usr/local/bin; + +// Not installed for simulator or Catalyst builds. +WK_LIB_JSC_TOOLS_SIMULATOR = NO; +WK_LIB_JSC_TOOLS_SIMULATOR[sdk=*simulator*] = YES; +WK_LIB_JSC_TOOLS_SUPPORTED = $(WK_NOT_$(WK_LIB_JSC_TOOLS_UNSUPPORTED)); +WK_LIB_JSC_TOOLS_UNSUPPORTED = $(WK_OR_$(WK_LIB_JSC_TOOLS_SIMULATOR)_$(WK_CHECK_CATALYST)); + +SKIP_INSTALL = $(WK_LIB_JSC_TOOLS_SKIP_INSTALL_$(WK_LIB_JSC_TOOLS_UNSUPPORTED)); +WK_LIB_JSC_TOOLS_SKIP_INSTALL_YES = YES; +WK_LIB_JSC_TOOLS_SKIP_INSTALL_NO = NO; + +OTHER_LDFLAGS = $(inherited) $(WK_LIB_JSC_TOOLS_LDFLAGS_$(WK_LIB_JSC_TOOLS_SUPPORTED)); +WK_LIB_JSC_TOOLS_LDFLAGS_YES = -lJavaScriptCoreTools; + +OTHER_CODE_SIGN_FLAGS[sdk=iphone*] = -i com.apple.jsc.mya --entitlements ${WK_PROCESSED_XCENT_FILE}; diff --git a/Source/JavaScriptCore/Configurations/TestLibJSCTools.xcconfig b/Source/JavaScriptCore/Configurations/TestLibJSCTools.xcconfig new file mode 100644 index 000000000000..6f33bf0408e5 --- /dev/null +++ b/Source/JavaScriptCore/Configurations/TestLibJSCTools.xcconfig @@ -0,0 +1,35 @@ +// Copyright (C) 2026 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 "TestExecutable.xcconfig" + +// We build and install testLibJSCTools for the simulator and MacCatalyst but it does nothing +// on these platforms. We do this so that run-javascriptcore-tests do not error out for a missing +// testLibJSCTools executable. +WK_LIB_JSC_TOOLS_SIMULATOR = NO; +WK_LIB_JSC_TOOLS_SIMULATOR[sdk=*simulator*] = YES; +WK_LIB_JSC_TOOLS_SUPPORTED = $(WK_NOT_$(WK_LIB_JSC_TOOLS_UNSUPPORTED)); +WK_LIB_JSC_TOOLS_UNSUPPORTED = $(WK_OR_$(WK_LIB_JSC_TOOLS_SIMULATOR)_$(WK_CHECK_CATALYST)); + +OTHER_LDFLAGS = $(inherited) $(WK_LIB_JSC_TOOLS_LDFLAGS_$(WK_LIB_JSC_TOOLS_SUPPORTED)); +WK_LIB_JSC_TOOLS_LDFLAGS_YES = -lJavaScriptCoreTools; diff --git a/Source/JavaScriptCore/Configurations/libJavaScriptCoreTools.xcconfig b/Source/JavaScriptCore/Configurations/libJavaScriptCoreTools.xcconfig new file mode 100644 index 000000000000..3f9449f216f3 --- /dev/null +++ b/Source/JavaScriptCore/Configurations/libJavaScriptCoreTools.xcconfig @@ -0,0 +1,54 @@ +// Copyright (C) 2026 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. + +// JavaScriptCoreTools is a tools library that is for implementing tools to assist +// in JavaScriptCore and WebKit development. It builds on JavaScriptCore, but does +// not contain any functionality that is part of core JSC, and it is not needed for +// implementing browser engines. +// +// JavaScriptCoreTools is packaged as a static library, and meant only to be linked +// by tools that need it. Its headers are included as private headers of JavaScriptCore. +// This allows JavaScriptCore and JavaScriptCoreTools to be revision locked, and +// built in tandem to prevent bit rot from creeping in. +#include "BaseTarget.xcconfig" + +PRODUCT_NAME = JavaScriptCoreTools; + +// Not built for simulator or Catalyst builds. +WK_LIB_JSC_TOOLS_SIMULATOR = NO; +WK_LIB_JSC_TOOLS_SIMULATOR[sdk=*simulator*] = YES; +WK_LIB_JSC_TOOLS_UNSUPPORTED = $(WK_OR_$(WK_LIB_JSC_TOOLS_SIMULATOR)_$(WK_CHECK_CATALYST)); + +EXCLUDED_SOURCE_FILE_NAMES = $(WK_LIB_JSC_TOOLS_EXCLUDED_$(WK_LIB_JSC_TOOLS_UNSUPPORTED)); +WK_LIB_JSC_TOOLS_EXCLUDED_YES = *; + +// Installed to /usr/local/lib, alongside libWTF.a and libbmalloc.a. +INSTALL_PATH = $(WK_LIBRARY_INSTALL_PATH); +SKIP_INSTALL = $(WK_LIB_JSC_TOOLS_SKIP_INSTALL_$(WK_LIB_JSC_TOOLS_UNSUPPORTED)); +WK_LIB_JSC_TOOLS_SKIP_INSTALL_YES = YES; +WK_LIB_JSC_TOOLS_SKIP_INSTALL_NO = NO; +STRIP_INSTALLED_PRODUCT = NO; + +// The sources include "config.h" and their own headers from the source tree, +// plus JavaScriptCore's generated and private headers. +HEADER_SEARCH_PATHS = $(SRCROOT) $(SRCROOT)/corpse "$(JAVASCRIPTCORE_FRAMEWORKS_DIR)/JavaScriptCore.framework/PrivateHeaders" $(inherited); diff --git a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj index 62dc0c1cb3bd..eb7e1593d232 100644 --- a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj +++ b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ dependencies = ( 932F5BE70822A1C700736975 /* PBXTargetDependency */, 5D69E912152BE5470028D720 /* PBXTargetDependency */, + D3934A4385F631C04057F7E7 /* PBXTargetDependency */, + 5F40739EF054554602787C41 /* PBXTargetDependency */, 5D6B2A57152B9E2E005231DE /* PBXTargetDependency */, ); name = All; @@ -1527,6 +1529,9 @@ 92B4EF902D71C3650068CB55 /* LLVMProfiling.h in Headers */ = {isa = PBXBuildFile; fileRef = 92B4EF8F2D71C3650068CB55 /* LLVMProfiling.h */; settings = {ATTRIBUTES = (Private, ); }; }; 93052C350FB792190048FDC3 /* ParserArena.h in Headers */ = {isa = PBXBuildFile; fileRef = 93052C330FB792190048FDC3 /* ParserArena.h */; settings = {ATTRIBUTES = (Private, ); }; }; 932F5BDD0822A1C700736975 /* jsc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45E12D8806A49B0F00E9DF84 /* jsc.cpp */; }; + 6DCC8B386903B87B8A4E5A26 /* mya.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 806620AA0612E27A5093C379 /* mya.cpp */; }; + ACB50B356C9AA8E90AA3DF50 /* JavaScriptCore.framework in Product Dependencies */ = {isa = PBXBuildFile; fileRef = 932F5BD90822A1C700736975 /* JavaScriptCore.framework */; }; + D9BBE216687E30084BD44207 /* libedit.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D5D8AD00E0D0EBE00F9C692 /* libedit.dylib */; }; 933040040E6A749400786E6A /* SmallStrings.h in Headers */ = {isa = PBXBuildFile; fileRef = 93303FEA0E6A72C000786E6A /* SmallStrings.h */; settings = {ATTRIBUTES = (Private, ); }; }; 93BFC6D929B344C90030D7BE /* GlobalObjectMethodTable.h in Headers */ = {isa = PBXBuildFile; fileRef = 93BFC6D829B344C80030D7BE /* GlobalObjectMethodTable.h */; settings = {ATTRIBUTES = (Private, ); }; }; 95CA6AD328809E010062D5EC /* ImplementationVisibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 95CA6AD228809E010062D5EC /* ImplementationVisibility.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -1828,6 +1833,34 @@ BC11667B0E199C05008066DD /* InternalFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11667A0E199C05008066DD /* InternalFunction.h */; settings = {ATTRIBUTES = (Private, ); }; }; BC1167DA0E19BCC9008066DD /* JSCell.h in Headers */ = {isa = PBXBuildFile; fileRef = BC1167D80E19BCC9008066DD /* JSCell.h */; settings = {ATTRIBUTES = (Private, ); }; }; BC18C3E50E16F5CD00B34460 /* APICast.h in Headers */ = {isa = PBXBuildFile; fileRef = 1482B78A0A4305AB00517CFC /* APICast.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 0D9C2E68ACFF010D91916993 /* CorpseProcess.h in Headers */ = {isa = PBXBuildFile; fileRef = 55D0F2DD9CA70132D5104B34 /* CorpseProcess.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 3A7C1E5D9B0F4A2681C34D07 /* CorpseRegion.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F41D6082A3E4B57C0768DB1 /* CorpseRegion.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 725D045D67B3017EAC56561C /* CorpseAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C6EB9C72F84ED87DF23277A /* CorpseAddress.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 80B96670131FB9EBB9AD49CC /* CorpseClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CBE14E70FB474FCFCD6A8B9 /* CorpseClient.h */; settings = {ATTRIBUTES = (Private, ); }; }; + C2EB941F3198AE0BD86A2324 /* CorpseClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 422D2FE4AA2401EA8C3F2D8F /* CorpseClient.cpp */; }; + 85428E7BE0B3557DD2988043 /* CorpseError.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DDA7A78AD90B64B97AE329C /* CorpseError.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 121D90BB8171A66F1922D548 /* CorpseError.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2AC15166B6F71CD338DD8D6F /* CorpseError.cpp */; }; + 7E83C43817D785F3003DC41B /* CorpseExportsTrie.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C43617D785F3003DC41B /* CorpseExportsTrie.cpp */; }; + 7E83C42417D785F3003DC41B /* CorpseByteParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C42317D785F3003DC41B /* CorpseByteParser.cpp */; }; + 7E83C42817D785F3003DC41B /* CorpseByteParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E83C42217D785F3003DC41B /* CorpseByteParser.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 7E83C42717D785F3003DC41B /* CorpseByteParserTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C42617D785F3003DC41B /* CorpseByteParserTest.cpp */; }; + 7E83C42B17D785F3003DC41B /* CorpseAddressTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C42917D785F3003DC41B /* CorpseAddressTest.cpp */; }; + 7E83C42E17D785F3003DC41B /* CorpseProcessTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C42C17D785F3003DC41B /* CorpseProcessTest.cpp */; }; + 7E83C43117D785F3003DC41B /* CorpseRegionTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C42F17D785F3003DC41B /* CorpseRegionTest.cpp */; }; + 7E83C43417D785F3003DC41B /* CorpseThreadTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C43217D785F3003DC41B /* CorpseThreadTest.cpp */; }; + 7E83C40B17D785F3003DC41B /* CorpseExportsTrieTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C40217D785F3003DC41B /* CorpseExportsTrieTest.cpp */; }; + 7E83C41017D785F3003DC41B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FF0F569B2E334C90002A232A /* Foundation.framework */; }; + 7E83C40F17D785F3003DC41B /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 932F5BD90822A1C700736975 /* JavaScriptCore.framework */; }; + 7E83C40C17D785F3003DC41B /* LibJSCToolsTestUtilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C40417D785F3003DC41B /* LibJSCToolsTestUtilities.cpp */; }; + 7E83C40D17D785F3003DC41B /* CorpseSnapshotTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C40617D785F3003DC41B /* CorpseSnapshotTest.cpp */; }; + 7E83C40E17D785F3003DC41B /* CorpseSymbolTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C40817D785F3003DC41B /* CorpseSymbolTest.cpp */; }; + 7E83C40A17D785F3003DC41B /* testLibJSCTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E83C40017D785F3003DC41B /* testLibJSCTools.cpp */; }; + 7E83C43717D785F3003DC41B /* CorpseExportsTrie.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E83C43517D785F3003DC41B /* CorpseExportsTrie.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 56C6C2B7EBDCC63B6ECEA9AD /* CorpseThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 211E3D6CA97F31CEB385A75A /* CorpseThread.h */; settings = {ATTRIBUTES = (Private, ); }; }; + EF648B9EEAF54C44048B416A /* CorpseThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 960BD2B783EAEBE3233F91BF /* CorpseThread.cpp */; }; + CA47D093D003440A28D15924 /* CorpseSnapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = 94711688984F16F71D615DE5 /* CorpseSnapshot.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 5601486802533982ACC870BE /* CorpseSymbol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F1FA0900D09DF06EE63F7D2 /* CorpseSymbol.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 377C8C2416AE3DF90650C0E9 /* CorpseSymbol.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E8D1735B28066A3B1D756ECC /* CorpseSymbol.cpp */; }; BC18C3E60E16F5CD00B34460 /* ArrayConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = BC7952070E15E8A800A898AB /* ArrayConstructor.h */; settings = {ATTRIBUTES = (Private, ); }; }; BC18C3E70E16F5CD00B34460 /* ArrayPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = F692A84E0255597D01FF60F7 /* ArrayPrototype.h */; settings = {ATTRIBUTES = (Private, ); }; }; BC18C3EC0E16F5CD00B34460 /* BooleanObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 704FD35305697E6D003DBED9 /* BooleanObject.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -2131,6 +2164,9 @@ E392E6F924D25FA900B20767 /* B3BottomTupleValue.h in Headers */ = {isa = PBXBuildFile; fileRef = E392E6F724D25FA600B20767 /* B3BottomTupleValue.h */; }; E393ADD81FE702D00022D681 /* WeakMapImplInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E393ADD71FE702CC0022D681 /* WeakMapImplInlines.h */; }; E39440542F276A4A0055F0DB /* Binja.c in Sources */ = {isa = PBXBuildFile; fileRef = E3380F572F271A400097D76C /* Binja.c */; }; + C366EA5039A3B51405995EA6 /* CorpseProcess.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68BBF9CBD85C8635B2B8385F /* CorpseProcess.cpp */; }; + 5E2B94A17C6D40F3B85219CE /* CorpseRegion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C8503B7E641A29DF5B0E3742 /* CorpseRegion.cpp */; }; + 0946712DEC51E0E86C4A49E3 /* CorpseSnapshot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E906BE4CC29012A83A8F299 /* CorpseSnapshot.cpp */; }; E3952C182F1DDF5700F5BEE8 /* B3WasmStructGetValue.h in Headers */ = {isa = PBXBuildFile; fileRef = E3952C122F1DDF5700F5BEE8 /* B3WasmStructGetValue.h */; }; E3952C192F1DDF5700F5BEE8 /* B3WasmStructNewValue.h in Headers */ = {isa = PBXBuildFile; fileRef = E3952C142F1DDF5700F5BEE8 /* B3WasmStructNewValue.h */; }; E3952C1A2F1DDF5700F5BEE8 /* B3WasmStructFieldValue.h in Headers */ = {isa = PBXBuildFile; fileRef = E3952C102F1DDF5700F5BEE8 /* B3WasmStructFieldValue.h */; }; @@ -2527,6 +2563,48 @@ /* End PBXBuildRule section */ /* Begin PBXContainerItemProxy section */ + 6044D4B120B01FBCA9BCFBE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 49D9C56EC7911960C24239F3; + remoteInfo = JavaScriptCoreTools; + }; + A1B2C3000AE5B4A700C0FFEE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 65FB3F6609D11E9100F49DEB; + remoteInfo = "Derived Sources"; + }; + A1B2C30004E5B4A700C0FFEE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 49D9C56EC7911960C24239F3; + remoteInfo = JavaScriptCoreTools; + }; + A1B2C30006E5B4A700C0FFEE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6B03652E1F50D87F0DEC6B42; + remoteInfo = mya; + }; + A1B2C30008E5B4A700C0FFEE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7E83C41317D785F3003DC41B; + remoteInfo = testLibJSCTools; + }; + 291A8D565940996D3CA021C1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 49D9C56EC7911960C24239F3; + remoteInfo = JavaScriptCoreTools; + }; 074D7E092E3D3B6800CD38C6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; @@ -2646,6 +2724,20 @@ remoteGlobalIDString = 65FB3F6609D11E9100F49DEB; remoteInfo = "Derived Sources"; }; + B70B8C003BC4F51DF41C2D65 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 65FB3F6609D11E9100F49DEB; + remoteInfo = "Derived Sources"; + }; + A269FDF04AF38423A3A5DE9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6B03652E1F50D87F0DEC6B42; + remoteInfo = mya; + }; 44F93E102AE7200100FFA37C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; @@ -2758,6 +2850,27 @@ remoteGlobalIDString = FE533CA11F217DB30016A1FE; remoteInfo = testmasm; }; + 7E83C41C17D785F3003DC41B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 65FB3F6609D11E9100F49DEB; + remoteInfo = "Derived Sources"; + }; + 7E83C41E17D785F3003DC41B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E1AC2E2720F7B94C00B0897D; + remoteInfo = "Unlock Keychain"; + }; + 7E83C42017D785F3003DC41B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7E83C41317D785F3003DC41B; + remoteInfo = testLibJSCTools; + }; FF0F56882E33437C002A232A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; @@ -2815,6 +2928,17 @@ name = "Product Dependencies"; runOnlyForDeploymentPostprocessing = 0; }; + 184CD487A9C6C6DCE0BA2ADA /* Product Dependencies */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 16; + files = ( + ACB50B356C9AA8E90AA3DF50 /* JavaScriptCore.framework in Product Dependencies */, + ); + name = "Product Dependencies"; + runOnlyForDeploymentPostprocessing = 0; + }; 5DBB1524131D0BA10056AD36 /* Copy Support Script */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -3951,6 +4075,23 @@ 1482B74B0A43032800517CFC /* JSStringRef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSStringRef.h; sourceTree = ""; }; 1482B74C0A43032800517CFC /* JSStringRef.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSStringRef.cpp; sourceTree = ""; }; 1482B78A0A4305AB00517CFC /* APICast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APICast.h; sourceTree = ""; }; + 55D0F2DD9CA70132D5104B34 /* CorpseProcess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseProcess.h; sourceTree = ""; }; + 9F41D6082A3E4B57C0768DB1 /* CorpseRegion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseRegion.h; sourceTree = ""; }; + 94711688984F16F71D615DE5 /* CorpseSnapshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseSnapshot.h; sourceTree = ""; }; + 8F1FA0900D09DF06EE63F7D2 /* CorpseSymbol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseSymbol.h; sourceTree = ""; }; + E8D1735B28066A3B1D756ECC /* CorpseSymbol.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseSymbol.cpp; sourceTree = ""; }; + 68BBF9CBD85C8635B2B8385F /* CorpseProcess.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseProcess.cpp; sourceTree = ""; }; + C8503B7E641A29DF5B0E3742 /* CorpseRegion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseRegion.cpp; sourceTree = ""; }; + 5E906BE4CC29012A83A8F299 /* CorpseSnapshot.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseSnapshot.cpp; sourceTree = ""; }; + 2C6EB9C72F84ED87DF23277A /* CorpseAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseAddress.h; sourceTree = ""; }; + 9CBE14E70FB474FCFCD6A8B9 /* CorpseClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseClient.h; sourceTree = ""; }; + 422D2FE4AA2401EA8C3F2D8F /* CorpseClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseClient.cpp; sourceTree = ""; }; + 8DDA7A78AD90B64B97AE329C /* CorpseError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseError.h; sourceTree = ""; }; + 2AC15166B6F71CD338DD8D6F /* CorpseError.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseError.cpp; sourceTree = ""; }; + 7E83C43617D785F3003DC41B /* CorpseExportsTrie.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseExportsTrie.cpp; sourceTree = ""; }; + 7E83C43517D785F3003DC41B /* CorpseExportsTrie.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseExportsTrie.h; sourceTree = ""; }; + 211E3D6CA97F31CEB385A75A /* CorpseThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseThread.h; sourceTree = ""; }; + 960BD2B783EAEBE3233F91BF /* CorpseThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseThread.cpp; sourceTree = ""; }; 1482B7E10A43076000517CFC /* JSObjectRef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSObjectRef.h; sourceTree = ""; }; 1482B7E20A43076000517CFC /* JSObjectRef.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSObjectRef.cpp; sourceTree = ""; }; 148521D526EAEBDF00CC1D1A /* WasmHandlerInfo.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WasmHandlerInfo.cpp; sourceTree = ""; }; @@ -4347,9 +4488,35 @@ 4487DB822AF825C800AFECAE /* Fuzzilli.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Fuzzilli.h; sourceTree = ""; }; 44F93DFD2AE71EBD00FFA37C /* libJavaScriptCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = libJavaScriptCore.xcconfig; sourceTree = ""; }; 44F93E022AE71F5400FFA37C /* libJavaScriptCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libJavaScriptCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CCFE0660BE540320EF4777 /* libJavaScriptCoreTools.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = libJavaScriptCoreTools.xcconfig; sourceTree = ""; }; + A1B2C30001E5B4A700C0FFEE /* TestLibJSCTools.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TestLibJSCTools.xcconfig; sourceTree = ""; }; + 76A3C1425B4D63FC23BD2344 /* libJavaScriptCoreTools.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libJavaScriptCoreTools.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E83C42317D785F3003DC41B /* CorpseByteParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseByteParser.cpp; sourceTree = ""; }; + 7E83C42217D785F3003DC41B /* CorpseByteParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseByteParser.h; sourceTree = ""; }; + 7E83C42617D785F3003DC41B /* CorpseByteParserTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseByteParserTest.cpp; sourceTree = ""; }; + 7E83C42917D785F3003DC41B /* CorpseAddressTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseAddressTest.cpp; sourceTree = ""; }; + 7E83C42A17D785F3003DC41B /* CorpseAddressTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseAddressTest.h; sourceTree = ""; }; + 7E83C42C17D785F3003DC41B /* CorpseProcessTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseProcessTest.cpp; sourceTree = ""; }; + 7E83C42D17D785F3003DC41B /* CorpseProcessTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseProcessTest.h; sourceTree = ""; }; + 7E83C42F17D785F3003DC41B /* CorpseRegionTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseRegionTest.cpp; sourceTree = ""; }; + 7E83C43017D785F3003DC41B /* CorpseRegionTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseRegionTest.h; sourceTree = ""; }; + 7E83C43217D785F3003DC41B /* CorpseThreadTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseThreadTest.cpp; sourceTree = ""; }; + 7E83C43317D785F3003DC41B /* CorpseThreadTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseThreadTest.h; sourceTree = ""; }; + 7E83C42517D785F3003DC41B /* CorpseByteParserTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseByteParserTest.h; sourceTree = ""; }; + 7E83C40217D785F3003DC41B /* CorpseExportsTrieTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseExportsTrieTest.cpp; sourceTree = ""; }; + 7E83C40117D785F3003DC41B /* CorpseExportsTrieTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseExportsTrieTest.h; sourceTree = ""; }; + 7E83C40417D785F3003DC41B /* LibJSCToolsTestUtilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LibJSCToolsTestUtilities.cpp; sourceTree = ""; }; + 7E83C40317D785F3003DC41B /* LibJSCToolsTestUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LibJSCToolsTestUtilities.h; sourceTree = ""; }; + 7E83C40617D785F3003DC41B /* CorpseSnapshotTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseSnapshotTest.cpp; sourceTree = ""; }; + 7E83C40517D785F3003DC41B /* CorpseSnapshotTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseSnapshotTest.h; sourceTree = ""; }; + 7E83C40817D785F3003DC41B /* CorpseSymbolTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CorpseSymbolTest.cpp; sourceTree = ""; }; + 7E83C40717D785F3003DC41B /* CorpseSymbolTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CorpseSymbolTest.h; sourceTree = ""; }; + 7E83C40017D785F3003DC41B /* testLibJSCTools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = testLibJSCTools.cpp; sourceTree = ""; }; + 7E83C40917D785F3003DC41B /* testLibJSCTools */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = testLibJSCTools; sourceTree = BUILT_PRODUCTS_DIR; }; 44F93E0D2AE71F9F00FFA37C /* JavaScriptCoreFramework.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JavaScriptCoreFramework.cpp; sourceTree = ""; }; 451539B812DC994500EF7AC4 /* Yarr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Yarr.h; path = yarr/Yarr.h; sourceTree = ""; }; 45E12D8806A49B0F00E9DF84 /* jsc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsc.cpp; sourceTree = ""; tabWidth = 4; }; + 806620AA0612E27A5093C379 /* mya.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mya.cpp; path = mya/mya.cpp; sourceTree = ""; tabWidth = 4; }; 4615E4662B5833FB001D4D53 /* WasmBBQJIT64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WasmBBQJIT64.h; sourceTree = ""; }; 4615E4682B5833FB001D4D53 /* WasmBBQJIT64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WasmBBQJIT64.cpp; sourceTree = ""; }; 4B78E098294427D2003C6682 /* B3SIMDValue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = B3SIMDValue.cpp; path = b3/B3SIMDValue.cpp; sourceTree = ""; }; @@ -4752,6 +4919,7 @@ 5C7E1A152DA1B0E100A4C005 /* JSTypedArrayViewPrototypeInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSTypedArrayViewPrototypeInternal.h; sourceTree = ""; }; 5D5D8AD00E0D0EBE00F9C692 /* libedit.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libedit.dylib; path = /usr/lib/libedit.dylib; sourceTree = ""; }; 5DAFD6CB146B686300FBEFB4 /* JSC.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = JSC.xcconfig; sourceTree = ""; }; + 3E812A1EEBE0D5AEA7D3C74B /* Mya.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Mya.xcconfig; sourceTree = ""; }; 5DE3D0F40DD8DDFB00468714 /* WebKitAvailability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebKitAvailability.h; sourceTree = ""; }; 623A37EB1B87A7BD00754209 /* RegisterMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegisterMap.h; sourceTree = ""; }; 627673211B680C1E00FD9F2E /* CallMode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CallMode.cpp; sourceTree = ""; }; @@ -5183,6 +5351,7 @@ 932F5BD80822A1C700736975 /* Info.plist */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; tabWidth = 8; usesTabs = 1; }; 932F5BD90822A1C700736975 /* JavaScriptCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JavaScriptCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 932F5BE10822A1C700736975 /* jsc */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jsc; sourceTree = BUILT_PRODUCTS_DIR; }; + B2D6E3DABD6662589CD25896 /* mya */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mya; sourceTree = BUILT_PRODUCTS_DIR; }; 93303FE80E6A72B500786E6A /* SmallStrings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SmallStrings.cpp; sourceTree = ""; }; 93303FEA0E6A72C000786E6A /* SmallStrings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmallStrings.h; sourceTree = ""; }; 933A349A038AE7C6008635CE /* Identifier.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = Identifier.h; sourceTree = ""; tabWidth = 8; }; @@ -6726,6 +6895,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 97663BCF9AB10EBDF8B13940 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D9BBE216687E30084BD44207 /* libedit.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FE533CA41F217DB30016A1FE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -6735,6 +6912,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7E83C41A17D785F3003DC41B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E83C41017D785F3003DC41B /* Foundation.framework in Frameworks */, + 7E83C40F17D785F3003DC41B /* JavaScriptCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FF0F568C2E33437C002A232A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -6747,11 +6933,65 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7E83C41217D785F3003DC41B /* tests */ = { + isa = PBXGroup; + children = ( + 7E83C42917D785F3003DC41B /* CorpseAddressTest.cpp */, + 7E83C42A17D785F3003DC41B /* CorpseAddressTest.h */, + 7E83C42617D785F3003DC41B /* CorpseByteParserTest.cpp */, + 7E83C42517D785F3003DC41B /* CorpseByteParserTest.h */, + 7E83C40217D785F3003DC41B /* CorpseExportsTrieTest.cpp */, + 7E83C40117D785F3003DC41B /* CorpseExportsTrieTest.h */, + 7E83C42C17D785F3003DC41B /* CorpseProcessTest.cpp */, + 7E83C42D17D785F3003DC41B /* CorpseProcessTest.h */, + 7E83C42F17D785F3003DC41B /* CorpseRegionTest.cpp */, + 7E83C43017D785F3003DC41B /* CorpseRegionTest.h */, + 7E83C40617D785F3003DC41B /* CorpseSnapshotTest.cpp */, + 7E83C40517D785F3003DC41B /* CorpseSnapshotTest.h */, + 7E83C40817D785F3003DC41B /* CorpseSymbolTest.cpp */, + 7E83C40717D785F3003DC41B /* CorpseSymbolTest.h */, + 7E83C43217D785F3003DC41B /* CorpseThreadTest.cpp */, + 7E83C43317D785F3003DC41B /* CorpseThreadTest.h */, + 7E83C40417D785F3003DC41B /* LibJSCToolsTestUtilities.cpp */, + 7E83C40317D785F3003DC41B /* LibJSCToolsTestUtilities.h */, + 7E83C40017D785F3003DC41B /* testLibJSCTools.cpp */, + ); + path = tests; + sourceTree = ""; + }; + 65036098A29083DFB9FCF27C /* corpse */ = { + isa = PBXGroup; + children = ( + 7E83C41217D785F3003DC41B /* tests */, + 2C6EB9C72F84ED87DF23277A /* CorpseAddress.h */, + 7E83C42317D785F3003DC41B /* CorpseByteParser.cpp */, + 7E83C42217D785F3003DC41B /* CorpseByteParser.h */, + 422D2FE4AA2401EA8C3F2D8F /* CorpseClient.cpp */, + 9CBE14E70FB474FCFCD6A8B9 /* CorpseClient.h */, + 2AC15166B6F71CD338DD8D6F /* CorpseError.cpp */, + 8DDA7A78AD90B64B97AE329C /* CorpseError.h */, + 7E83C43617D785F3003DC41B /* CorpseExportsTrie.cpp */, + 7E83C43517D785F3003DC41B /* CorpseExportsTrie.h */, + 68BBF9CBD85C8635B2B8385F /* CorpseProcess.cpp */, + 55D0F2DD9CA70132D5104B34 /* CorpseProcess.h */, + C8503B7E641A29DF5B0E3742 /* CorpseRegion.cpp */, + 9F41D6082A3E4B57C0768DB1 /* CorpseRegion.h */, + 5E906BE4CC29012A83A8F299 /* CorpseSnapshot.cpp */, + 94711688984F16F71D615DE5 /* CorpseSnapshot.h */, + E8D1735B28066A3B1D756ECC /* CorpseSymbol.cpp */, + 8F1FA0900D09DF06EE63F7D2 /* CorpseSymbol.h */, + 960BD2B783EAEBE3233F91BF /* CorpseThread.cpp */, + 211E3D6CA97F31CEB385A75A /* CorpseThread.h */, + ); + path = corpse; + sourceTree = ""; + }; 034768DFFF38A50411DB9C8B /* Products */ = { isa = PBXGroup; children = ( 0F9327591C20BCBA00CF6564 /* dynbench */, 932F5BE10822A1C700736975 /* jsc */, + B2D6E3DABD6662589CD25896 /* mya */, 0FF922CF14F46B130041A24E /* JSCLLIntOffsetsExtractor */, 14BD688E215191310050DAFF /* JSCLLIntSettingsExtractor */, 141211200A48793C00480255 /* minidom */, @@ -6761,9 +7001,11 @@ 52CD0F642242F569004A18A5 /* testdfg */, FE533CAC1F217DB40016A1FE /* testmasm */, 79281BDC20B62B3E002E2A60 /* testmem */, + 7E83C40917D785F3003DC41B /* testLibJSCTools */, 6511230514046A4C002B101D /* testRegExp */, 932F5BD90822A1C700736975 /* JavaScriptCore.framework */, 44F93E022AE71F5400FFA37C /* libJavaScriptCore.a */, + 76A3C1425B4D63FC23BD2344 /* libJavaScriptCoreTools.a */, FF0F56942E33437C002A232A /* testwasmdebugger */, ); name = Products; @@ -6795,6 +7037,7 @@ 44F93E0D2AE71F9F00FFA37C /* JavaScriptCoreFramework.cpp */, F5C290E60284F98E018635CA /* JavaScriptCorePrefix.h */, 45E12D8806A49B0F00E9DF84 /* jsc.cpp */, + 806620AA0612E27A5093C379 /* mya.cpp */, A7C225CC139981F100FF1662 /* KeywordLookupGenerator.py */, 79D7B0E121152FD200FE7C64 /* entitlements.plist */, 53ADF4742F0D7A2000A05CDD /* lol */, @@ -6804,6 +7047,7 @@ A7D8019F1880D66E0026C39B /* builtins */, 969A078F0ED1D3AE00F1F681 /* bytecode */, 7E39D81D0EC38EFA003AF11A /* bytecompiler */, + 65036098A29083DFB9FCF27C /* corpse */, 1C90513E0BA9E8830081E9D0 /* Configurations */, 1480DB9A0DDC2231003CFDF2 /* debugger */, 650FDF8D09D0FCA700769E54 /* Derived Sources */, @@ -7952,8 +8196,11 @@ 1C9051430BA9E8A70081E9D0 /* JavaScriptCore.xcconfig */, 5DAFD6CB146B686300FBEFB4 /* JSC.xcconfig */, 44F93DFD2AE71EBD00FFA37C /* libJavaScriptCore.xcconfig */, + 33CCFE0660BE540320EF4777 /* libJavaScriptCoreTools.xcconfig */, DD8A31502F17A00000000001 /* LLIntExtractor.xcconfig */, + 3E812A1EEBE0D5AEA7D3C74B /* Mya.xcconfig */, FEE0A12229FE250400CED5E4 /* TestExecutable.xcconfig */, + A1B2C30001E5B4A700C0FFEE /* TestLibJSCTools.xcconfig */, BC021BF2136900C300FC5467 /* ToolExecutable.xcconfig */, ); path = Configurations; @@ -11438,6 +11685,16 @@ E3FCCB642310A90D00238E72 /* ConstructorKind.h in Headers */, A57D23F21891B5B40031C7FA /* ContentSearchUtilities.h in Headers */, 52678F911A04177C006A306D /* ControlFlowProfiler.h in Headers */, + 725D045D67B3017EAC56561C /* CorpseAddress.h in Headers */, + 7E83C42817D785F3003DC41B /* CorpseByteParser.h in Headers */, + 80B96670131FB9EBB9AD49CC /* CorpseClient.h in Headers */, + 85428E7BE0B3557DD2988043 /* CorpseError.h in Headers */, + 7E83C43717D785F3003DC41B /* CorpseExportsTrie.h in Headers */, + 0D9C2E68ACFF010D91916993 /* CorpseProcess.h in Headers */, + 3A7C1E5D9B0F4A2681C34D07 /* CorpseRegion.h in Headers */, + CA47D093D003440A28D15924 /* CorpseSnapshot.h in Headers */, + 5601486802533982ACC870BE /* CorpseSymbol.h in Headers */, + 56C6C2B7EBDCC63B6ECEA9AD /* CorpseThread.h in Headers */, C4F4B6F41A05C944005CAB76 /* cpp_generator.py in Headers */, C4F4B6F31A05C944005CAB76 /* cpp_generator_templates.py in Headers */, 0F30D7C01D95D6320053089D /* CPU.h in Headers */, @@ -13005,6 +13262,22 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 49D9C56EC7911960C24239F3 /* JavaScriptCoreTools */ = { + isa = PBXNativeTarget; + buildConfigurationList = CDAB8A13AA6F0C8A7D95A0DD /* Build configuration list for PBXNativeTarget "JavaScriptCoreTools" */; + buildPhases = ( + 0F73B81D27D8B848DDFD4BA2 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + A1B2C3000BE5B4A700C0FFEE /* PBXTargetDependency */, + ); + name = JavaScriptCoreTools; + productName = JavaScriptCoreTools; + productReference = 76A3C1425B4D63FC23BD2344 /* libJavaScriptCoreTools.a */; + productType = "com.apple.product-type.library.static"; + }; 0F6183381C45F62A0072450B /* testair */ = { isa = PBXNativeTarget; buildConfigurationList = 0F61833E1C45F62A0072450B /* Build configuration list for PBXNativeTarget "testair" */; @@ -13261,6 +13534,9 @@ buildRules = ( ); dependencies = ( + A1B2C30005E5B4A700C0FFEE /* PBXTargetDependency */, + A1B2C30007E5B4A700C0FFEE /* PBXTargetDependency */, + A1B2C30009E5B4A700C0FFEE /* PBXTargetDependency */, 14D9D9DA218462B5009126C2 /* PBXTargetDependency */, ); name = jsc; @@ -13269,6 +13545,27 @@ productReference = 932F5BE10822A1C700736975 /* jsc */; productType = "com.apple.product-type.tool"; }; + 6B03652E1F50D87F0DEC6B42 /* mya */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1564F766F55B0B045D183322 /* Build configuration list for PBXNativeTarget "mya" */; + buildPhases = ( + 184CD487A9C6C6DCE0BA2ADA /* Product Dependencies */, + FE35E0580C774FF6AC63188E /* Generate Entitlements */, + 4ECC923BDB875C54F15ECFB7 /* Sources */, + 97663BCF9AB10EBDF8B13940 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D3934A4385F631C04057F7E7 /* PBXTargetDependency */, + 87B6EB7D11A4B965B9269538 /* PBXTargetDependency */, + ); + name = mya; + productInstallPath = /usr/local/bin; + productName = mya; + productReference = B2D6E3DABD6662589CD25896 /* mya */; + productType = "com.apple.product-type.tool"; + }; FE533CA11F217DB30016A1FE /* testmasm */ = { isa = PBXNativeTarget; buildConfigurationList = FE533CA71F217DB30016A1FE /* Build configuration list for PBXNativeTarget "testmasm" */; @@ -13287,6 +13584,27 @@ productReference = FE533CAC1F217DB40016A1FE /* testmasm */; productType = "com.apple.product-type.tool"; }; + 7E83C41317D785F3003DC41B /* testLibJSCTools */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7E83C41417D785F3003DC41B /* Build configuration list for PBXNativeTarget "testLibJSCTools" */; + buildPhases = ( + 7E83C42117D785F3003DC41B /* Generate Entitlements */, + 7E83C41917D785F3003DC41B /* Sources */, + 7E83C41A17D785F3003DC41B /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F7DDEC2D6CAFD2ACDD19D991 /* PBXTargetDependency */, + 7E83C41B17D785F3003DC41B /* PBXTargetDependency */, + 7E83C41D17D785F3003DC41B /* PBXTargetDependency */, + ); + name = testLibJSCTools; + productInstallPath = /usr/local/bin; + productName = testLibJSCTools; + productReference = 7E83C40917D785F3003DC41B /* testLibJSCTools */; + productType = "com.apple.product-type.tool"; + }; FF0F56862E33437C002A232A /* testwasmdebugger */ = { isa = PBXNativeTarget; buildConfigurationList = FF0F568F2E33437C002A232A /* Build configuration list for PBXNativeTarget "testwasmdebugger" */; @@ -13354,6 +13672,8 @@ 1412111F0A48793C00480255 /* minidom */, 14BD59BE0A3E8F9000BAF59C /* testapi */, 932F5BDA0822A1C700736975 /* jsc */, + 49D9C56EC7911960C24239F3 /* JavaScriptCoreTools */, + 6B03652E1F50D87F0DEC6B42 /* mya */, 651122F714046A4C002B101D /* testRegExp */, 0FEC85941BDB5CF10080FF74 /* testb3 */, 5D6B2A47152B9E17005231DE /* Test Tools */, @@ -13364,6 +13684,7 @@ 5325BDBF21DFF2B100A0DEE1 /* Apply Configuration to XCFileLists */, 52CD0F592242F569004A18A5 /* testdfg */, FF0F56862E33437C002A232A /* testwasmdebugger */, + 7E83C41317D785F3003DC41B /* testLibJSCTools */, ); }; /* End PBXProject section */ @@ -13772,6 +14093,26 @@ shellPath = /bin/sh; shellScript = "Scripts/process-entitlements.sh\n"; }; + FE35E0580C774FF6AC63188E /* Generate Entitlements */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/Scripts/process-entitlements.sh", + ); + name = "Generate Entitlements"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(WK_PROCESSED_XCENT_FILE)", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "Scripts/process-entitlements.sh\n"; + }; E3D6F6EE25D78CF600C20EB4 /* Generate Entitlements */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -13976,6 +14317,26 @@ shellPath = /bin/sh; shellScript = "Scripts/process-entitlements.sh\n"; }; + 7E83C42117D785F3003DC41B /* Generate Entitlements */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/Scripts/process-entitlements.sh", + ); + name = "Generate Entitlements"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(WK_PROCESSED_XCENT_FILE)", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "Scripts/process-entitlements.sh\n"; + }; FF0F56892E33437C002A232A /* Generate Entitlements */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -13999,6 +14360,22 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 0F73B81D27D8B848DDFD4BA2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E83C42417D785F3003DC41B /* CorpseByteParser.cpp in Sources */, + C2EB941F3198AE0BD86A2324 /* CorpseClient.cpp in Sources */, + 121D90BB8171A66F1922D548 /* CorpseError.cpp in Sources */, + 7E83C43817D785F3003DC41B /* CorpseExportsTrie.cpp in Sources */, + C366EA5039A3B51405995EA6 /* CorpseProcess.cpp in Sources */, + 5E2B94A17C6D40F3B85219CE /* CorpseRegion.cpp in Sources */, + 0946712DEC51E0E86C4A49E3 /* CorpseSnapshot.cpp in Sources */, + 377C8C2416AE3DF90650C0E9 /* CorpseSymbol.cpp in Sources */, + EF648B9EEAF54C44048B416A /* CorpseThread.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 0F6183391C45F62A0072450B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -14345,6 +14722,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4ECC923BDB875C54F15ECFB7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6DCC8B386903B87B8A4E5A26 /* mya.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FE533CA21F217DB30016A1FE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -14353,6 +14738,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7E83C41917D785F3003DC41B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E83C42B17D785F3003DC41B /* CorpseAddressTest.cpp in Sources */, + 7E83C42717D785F3003DC41B /* CorpseByteParserTest.cpp in Sources */, + 7E83C40B17D785F3003DC41B /* CorpseExportsTrieTest.cpp in Sources */, + 7E83C42E17D785F3003DC41B /* CorpseProcessTest.cpp in Sources */, + 7E83C43117D785F3003DC41B /* CorpseRegionTest.cpp in Sources */, + 7E83C40D17D785F3003DC41B /* CorpseSnapshotTest.cpp in Sources */, + 7E83C40E17D785F3003DC41B /* CorpseSymbolTest.cpp in Sources */, + 7E83C43417D785F3003DC41B /* CorpseThreadTest.cpp in Sources */, + 7E83C40C17D785F3003DC41B /* LibJSCToolsTestUtilities.cpp in Sources */, + 7E83C40A17D785F3003DC41B /* testLibJSCTools.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FF0F568A2E33437C002A232A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -14377,6 +14779,31 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + D3934A4385F631C04057F7E7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 49D9C56EC7911960C24239F3 /* JavaScriptCoreTools */; + targetProxy = 6044D4B120B01FBCA9BCFBE5 /* PBXContainerItemProxy */; + }; + A1B2C30005E5B4A700C0FFEE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 49D9C56EC7911960C24239F3 /* JavaScriptCoreTools */; + targetProxy = A1B2C30004E5B4A700C0FFEE /* PBXContainerItemProxy */; + }; + A1B2C30007E5B4A700C0FFEE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6B03652E1F50D87F0DEC6B42 /* mya */; + targetProxy = A1B2C30006E5B4A700C0FFEE /* PBXContainerItemProxy */; + }; + A1B2C30009E5B4A700C0FFEE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7E83C41317D785F3003DC41B /* testLibJSCTools */; + targetProxy = A1B2C30008E5B4A700C0FFEE /* PBXContainerItemProxy */; + }; + F7DDEC2D6CAFD2ACDD19D991 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 49D9C56EC7911960C24239F3 /* JavaScriptCoreTools */; + targetProxy = 291A8D565940996D3CA021C1 /* PBXContainerItemProxy */; + }; 074D7E0A2E3D3B6800CD38C6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 65FB3F6609D11E9100F49DEB /* Derived Sources */; @@ -14462,6 +14889,21 @@ target = 65FB3F6609D11E9100F49DEB /* Derived Sources */; targetProxy = 14D9D9D9218462B5009126C2 /* PBXContainerItemProxy */; }; + 87B6EB7D11A4B965B9269538 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 65FB3F6609D11E9100F49DEB /* Derived Sources */; + targetProxy = B70B8C003BC4F51DF41C2D65 /* PBXContainerItemProxy */; + }; + A1B2C3000BE5B4A700C0FFEE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 65FB3F6609D11E9100F49DEB /* Derived Sources */; + targetProxy = A1B2C3000AE5B4A700C0FFEE /* PBXContainerItemProxy */; + }; + 5F40739EF054554602787C41 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6B03652E1F50D87F0DEC6B42 /* mya */; + targetProxy = A269FDF04AF38423A3A5DE9A /* PBXContainerItemProxy */; + }; 44F93E112AE7200100FFA37C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 44F93E012AE71F5300FFA37C /* libJavaScriptCore */; @@ -14542,6 +14984,21 @@ target = FE533CA11F217DB30016A1FE /* testmasm */; targetProxy = FE533CAE1F217EC60016A1FE /* PBXContainerItemProxy */; }; + 7E83C41B17D785F3003DC41B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 65FB3F6609D11E9100F49DEB /* Derived Sources */; + targetProxy = 7E83C41C17D785F3003DC41B /* PBXContainerItemProxy */; + }; + 7E83C41D17D785F3003DC41B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E1AC2E2720F7B94C00B0897D /* Unlock Keychain */; + targetProxy = 7E83C41E17D785F3003DC41B /* PBXContainerItemProxy */; + }; + 7E83C41F17D785F3003DC41B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7E83C41317D785F3003DC41B /* testLibJSCTools */; + targetProxy = 7E83C42017D785F3003DC41B /* PBXContainerItemProxy */; + }; FF0F56872E33437C002A232A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = E1AC2E2720F7B94C00B0897D /* Unlock Keychain */; @@ -14555,6 +15012,34 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 0496C9FAB31F908B0425279C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33CCFE0660BE540320EF4777 /* libJavaScriptCoreTools.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + CF17CD764111B2A78C3D8F10 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33CCFE0660BE540320EF4777 /* libJavaScriptCoreTools.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 1738026904B5B9E1BA357B8F /* Profiling */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33CCFE0660BE540320EF4777 /* libJavaScriptCoreTools.xcconfig */; + buildSettings = { + }; + name = Profiling; + }; + 1A9B847BE318F847F08771D9 /* Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33CCFE0660BE540320EF4777 /* libJavaScriptCoreTools.xcconfig */; + buildSettings = { + }; + name = Production; + }; 0F61833F1C45F62A0072450B /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = FEE0A12229FE250400CED5E4 /* TestExecutable.xcconfig */; @@ -14731,6 +15216,34 @@ }; name = Production; }; + 0D9FBE8C4B6D65ED3E83AB12 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E812A1EEBE0D5AEA7D3C74B /* Mya.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 39672BABA758BF6EC3C9BDE6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E812A1EEBE0D5AEA7D3C74B /* Mya.xcconfig */; + buildSettings = { + }; + name = Release; + }; + D433E29BE1849C8E3DEEF0BB /* Profiling */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E812A1EEBE0D5AEA7D3C74B /* Mya.xcconfig */; + buildSettings = { + }; + name = Profiling; + }; + 9FAFBEC1CDCCB2F48A335C3E /* Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E812A1EEBE0D5AEA7D3C74B /* Mya.xcconfig */; + buildSettings = { + }; + name = Production; + }; 149C276D08902AFE008A9EFC /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 1C9051430BA9E8A70081E9D0 /* JavaScriptCore.xcconfig */; @@ -15183,6 +15696,38 @@ }; name = Production; }; + 7E83C41517D785F3003DC41B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A1B2C30001E5B4A700C0FFEE /* TestLibJSCTools.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 7E83C41617D785F3003DC41B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A1B2C30001E5B4A700C0FFEE /* TestLibJSCTools.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 7E83C41717D785F3003DC41B /* Profiling */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A1B2C30001E5B4A700C0FFEE /* TestLibJSCTools.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profiling; + }; + 7E83C41817D785F3003DC41B /* Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A1B2C30001E5B4A700C0FFEE /* TestLibJSCTools.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Production; + }; FF0F56902E33437C002A232A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = FEE0A12229FE250400CED5E4 /* TestExecutable.xcconfig */; @@ -15218,6 +15763,17 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + CDAB8A13AA6F0C8A7D95A0DD /* Build configuration list for PBXNativeTarget "JavaScriptCoreTools" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0496C9FAB31F908B0425279C /* Debug */, + CF17CD764111B2A78C3D8F10 /* Release */, + 1738026904B5B9E1BA357B8F /* Profiling */, + 1A9B847BE318F847F08771D9 /* Production */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Production; + }; 0F61833E1C45F62A0072450B /* Build configuration list for PBXNativeTarget "testair" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -15295,6 +15851,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Production; }; + 1564F766F55B0B045D183322 /* Build configuration list for PBXNativeTarget "mya" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D9FBE8C4B6D65ED3E83AB12 /* Debug */, + 39672BABA758BF6EC3C9BDE6 /* Release */, + D433E29BE1849C8E3DEEF0BB /* Profiling */, + 9FAFBEC1CDCCB2F48A335C3E /* Production */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Production; + }; 149C276C08902AFE008A9EFC /* Build configuration list for PBXAggregateTarget "All" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -15449,6 +16016,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Production; }; + 7E83C41417D785F3003DC41B /* Build configuration list for PBXNativeTarget "testLibJSCTools" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7E83C41517D785F3003DC41B /* Debug */, + 7E83C41617D785F3003DC41B /* Release */, + 7E83C41717D785F3003DC41B /* Profiling */, + 7E83C41817D785F3003DC41B /* Production */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Production; + }; FF0F568F2E33437C002A232A /* Build configuration list for PBXNativeTarget "testwasmdebugger" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Source/JavaScriptCore/Scripts/process-entitlements.sh b/Source/JavaScriptCore/Scripts/process-entitlements.sh index ff396dcd52c6..8e8c00f5d4ff 100755 --- a/Source/JavaScriptCore/Scripts/process-entitlements.sh +++ b/Source/JavaScriptCore/Scripts/process-entitlements.sh @@ -69,6 +69,23 @@ function mac_process_testapi_entitlements() fi } +function mac_process_mya_entitlements() +{ + if [[ "${WK_USE_RESTRICTED_ENTITLEMENTS}" == YES ]] + then + plistbuddy Add :com.apple.private.cs.debugger bool YES + + if [[ "${WK_USE_FATAL_EXCEPTIONS}" == YES ]] + then + plistbuddy Add :com.apple.private.pac.exception bool YES + fi + + plistbuddy Add :com.apple.developer.kernel.extended-virtual-addressing bool YES + + plistbuddy Add :com.apple.developer.hardened-process bool YES + fi +} + # ======================================== # macCatalyst entitlements # ======================================== @@ -133,6 +150,23 @@ function maccatalyst_process_testapi_entitlements() fi } +function maccatalyst_process_mya_entitlements() +{ + if [[ "${WK_USE_RESTRICTED_ENTITLEMENTS}" == YES ]] + then + plistbuddy Add :com.apple.private.cs.debugger bool YES + + if [[ "${WK_USE_FATAL_EXCEPTIONS}" == YES ]] + then + plistbuddy Add :com.apple.private.pac.exception bool YES + fi + + plistbuddy Add :com.apple.developer.kernel.extended-virtual-addressing bool YES + + plistbuddy Add :com.apple.developer.hardened-process bool YES + fi +} + # ======================================== # iOS Family entitlements # ======================================== @@ -165,6 +199,22 @@ function ios_family_process_jsc_entitlements() plistbuddy Add :com.apple.developer.hardened-process bool YES } +function ios_family_process_mya_entitlements() +{ + if [[ "${WK_USE_RESTRICTED_ENTITLEMENTS}" == YES ]] + then + plistbuddy Add :com.apple.private.cs.debugger bool YES + fi + + if [[ "${WK_USE_FATAL_EXCEPTIONS}" == YES ]] + then + plistbuddy Add :com.apple.private.pac.exception bool YES + fi + + plistbuddy Add :com.apple.developer.kernel.extended-virtual-addressing bool YES + plistbuddy Add :com.apple.developer.hardened-process bool YES +} + rm -f "${WK_PROCESSED_XCENT_FILE}" plistbuddy Clear dict @@ -185,6 +235,9 @@ then "${PRODUCT_NAME}" == testmem || "${PRODUCT_NAME}" == testRegExp ]]; then mac_process_jsc_entitlements elif [[ "${PRODUCT_NAME}" == testapi ]]; then mac_process_testapi_entitlements + elif [[ "${PRODUCT_NAME}" == mya ]]; then mac_process_mya_entitlements + # testLibJSCTools only ever snapshots its own process, which needs no entitlement. + elif [[ "${PRODUCT_NAME}" == testLibJSCTools ]]; then true else echo "Unsupported/unknown product: ${PRODUCT_NAME}" fi elif [[ "${WK_PLATFORM_NAME}" == maccatalyst || "${WK_PLATFORM_NAME}" == iosmac ]] @@ -201,6 +254,9 @@ then "${PRODUCT_NAME}" == testmem || "${PRODUCT_NAME}" == testRegExp ]]; then maccatalyst_process_jsc_entitlements elif [[ "${PRODUCT_NAME}" == testapi ]]; then maccatalyst_process_testapi_entitlements + elif [[ "${PRODUCT_NAME}" == mya ]]; then maccatalyst_process_mya_entitlements + # testLibJSCTools only ever snapshots its own process, which needs no entitlement. + elif [[ "${PRODUCT_NAME}" == testLibJSCTools ]]; then true else echo "Unsupported/unknown product: ${PRODUCT_NAME}" fi elif [[ "${WK_PLATFORM_NAME}" == iphoneos || @@ -218,6 +274,9 @@ then "${PRODUCT_NAME}" == testmasm || "${PRODUCT_NAME}" == testmem || "${PRODUCT_NAME}" == testRegExp ]]; then ios_family_process_jsc_entitlements + elif [[ "${PRODUCT_NAME}" == mya ]]; then ios_family_process_mya_entitlements + # testLibJSCTools only ever snapshots its own process, which needs no entitlement. + elif [[ "${PRODUCT_NAME}" == testLibJSCTools ]]; then true else echo "Unsupported/unknown product: ${PRODUCT_NAME}" fi else diff --git a/Source/JavaScriptCore/corpse/CMakeLists.txt b/Source/JavaScriptCore/corpse/CMakeLists.txt new file mode 100644 index 000000000000..f36085a527cd --- /dev/null +++ b/Source/JavaScriptCore/corpse/CMakeLists.txt @@ -0,0 +1,32 @@ +set(JavaScriptCoreTools_LIBRARY_TYPE STATIC) + +set(JavaScriptCoreTools_SOURCES + CorpseByteParser.cpp + CorpseClient.cpp + CorpseError.cpp + CorpseExportsTrie.cpp + CorpseProcess.cpp + CorpseRegion.cpp + CorpseSnapshot.cpp + CorpseSymbol.cpp + CorpseThread.cpp +) + +set(JavaScriptCoreTools_PRIVATE_INCLUDE_DIRECTORIES + $ +) + +set(JavaScriptCoreTools_FRAMEWORKS + JavaScriptCore + WTF + bmalloc +) + +WEBKIT_LIBRARY_DECLARE(JavaScriptCoreTools) + +WEBKIT_INCLUDE_CONFIG_FILES_IF_EXISTS() + +WEBKIT_LIBRARY(JavaScriptCoreTools) + +# The corpse sources use JavaScriptCore's generated headers. +add_dependencies(JavaScriptCoreTools JavaScriptCore) diff --git a/Source/JavaScriptCore/corpse/CorpseAddress.h b/Source/JavaScriptCore/corpse/CorpseAddress.h new file mode 100644 index 000000000000..ac83749e83d2 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseAddress.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include + +#if CPU(ARM64E) +#include +#endif + +namespace JSC { +namespace Corpse { + +// An address in the target corpse process. A corpse address can never be dereferenced +// by accident. +class Address { +public: + Address() = default; + explicit Address(mach_vm_address_t value) + : m_value(value) + { + } + explicit Address(const void* pointer) + : m_value(reinterpret_cast(pointer)) + { + } + + mach_vm_address_t toMachVMAddress() const { return m_value; } + explicit operator bool() const { return m_value; } + template explicit operator T() const = delete; + + Address stripped() const + { +#if CPU(ARM64E) + // We don't know if this is a code or data pointer. The 2 have different number of + // bits. But we know that code pointers have more PAC bits. So, we'll conservatively + // use XPACI to strip the max number of PAC bits. + auto stripped = ptrauth_strip(reinterpret_cast(m_value), ptrauth_key_process_dependent_code); + + // While XPACI may have already stripped the MTE tag in data pointers as well, + // we don't want to assume that code pointer PAC bits will always cover the MTE + // nibble or non-zero data pointer top-bytes due to TBI (Top Byte Ignore). So, + // let's explicitly clear the top byte to be sure. + constexpr uintptr_t topByte = 0xffull << 56; + uintptr_t strippedInt = std::bit_cast(stripped); + strippedInt &= ~topByte; + + return Address(std::bit_cast(strippedInt)); +#else + return *this; +#endif + } + + friend bool operator==(Address, Address) = default; + friend auto operator<=>(Address, Address) = default; + + friend bool operator==(Address address, std::nullptr_t) { return !address.m_value; } + + Address operator+(uint64_t offset) const { return Address(m_value + offset); } + Address operator-(uint64_t offset) const { return Address(m_value - offset); } + + uint64_t operator-(Address other) const { return m_value - other.m_value; } + +private: + mach_vm_address_t m_value { 0 }; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseByteParser.cpp b/Source/JavaScriptCore/corpse/CorpseByteParser.cpp new file mode 100644 index 000000000000..ea65768b3344 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseByteParser.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseByteParser.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include + +namespace JSC { +namespace Corpse { + +std::optional ByteParser::consumeByte() +{ + if (m_position >= m_data.size()) + return std::nullopt; + return m_data[m_position++]; +} + +std::optional ByteParser::consumeULEB128() +{ + size_t start = m_position; + uint64_t result = 0; + if (WTF::LEBDecoder::decodeUInt64(m_data, m_position, result)) + return result; + m_position = start; + return std::nullopt; +} + +std::optional ByteParser::consumeCString() +{ + size_t start = m_position; + while (m_position < m_data.size() && m_data[m_position]) + ++m_position; + if (m_position >= m_data.size()) { + m_position = start; + return std::nullopt; + } + std::string_view result(spanReinterpretCast(m_data.subspan(start, m_position - start))); + ++m_position; // Consume the null terminator. + return result; +} + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseByteParser.h b/Source/JavaScriptCore/corpse/CorpseByteParser.h new file mode 100644 index 000000000000..f5c404e03c1b --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseByteParser.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include + +namespace JSC { +namespace Corpse { + +// A forward byte parser over a local buffer. Every read reports whether it got +// what it asked for, and a read that fails consumes nothing. +class ByteParser { +public: + ByteParser(std::span data, size_t position = 0) + : m_data(data) + , m_position(position) + { + } + + size_t position() const { return m_position; } + + std::optional consumeByte(); + + // Decodes the ULEB128 at the cursor. Returns nullopt if the buffer ends + // before the encoding does, or if the value will not fit in 64 bits. + // Untrusted data can hold either, and silently truncating one would yield a + // plausible wrong value instead of a detected failure. + std::optional consumeULEB128(); + + // Returns the null-terminated string at the cursor. Returns nullopt if the + // buffer ends before the terminator does: without that the trailing bytes of + // a truncated buffer read back as a complete string. + std::optional consumeCString(); + +private: + std::span m_data; + size_t m_position; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseClient.cpp b/Source/JavaScriptCore/corpse/CorpseClient.cpp new file mode 100644 index 000000000000..800479f273de --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseClient.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseClient.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSC { +namespace Corpse { + +ASCIILiteral Client::s_clientName = "JSC::Corpse"_s; // Default if not set. + +void Client::setName(ASCIILiteral name) +{ + if (!name.isEmpty()) + s_clientName = name; +} + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseClient.h b/Source/JavaScriptCore/corpse/CorpseClient.h new file mode 100644 index 000000000000..b5ab8c1c380a --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseClient.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include + +namespace JSC { +namespace Corpse { + +// Currently, this is only to allow the client application to set the client name +// during initialization so that error messages identify with the client instead +// of the corpse library. + +class Client { +public: + static void setName(ASCIILiteral); + static ASCIILiteral name() { return s_clientName; } + +private: + static ASCIILiteral s_clientName; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseError.cpp b/Source/JavaScriptCore/corpse/CorpseError.cpp new file mode 100644 index 000000000000..0f7ea774d502 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseError.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseError.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "CorpseClient.h" + +#include +#include + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN + +namespace JSC { +namespace Corpse { + +void Error::report(const char* format, ...) +{ + fprintf(stderr, "%s: ", Client::name().characters()); + + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + fputc('\n', stderr); +} + +} // namespace Corpse +} // namespace JSC + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_END + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseError.h b/Source/JavaScriptCore/corpse/CorpseError.h new file mode 100644 index 000000000000..6a4ab690dd75 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseError.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include + +namespace JSC { +namespace Corpse { + +// Reports the library's diagnostics. Messages are prefixed with the name the +// client set via Corpse::Client, so they read as the client's own output. +class Error { +public: + static void report(const char* format, ...) WTF_ATTRIBUTE_PRINTF(1, 2); +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseExportsTrie.cpp b/Source/JavaScriptCore/corpse/CorpseExportsTrie.cpp new file mode 100644 index 000000000000..da6fa47ec71a --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseExportsTrie.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseExportsTrie.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include + +namespace JSC { +namespace Corpse { + +// The bits that Mach-O defines in an exports trie terminal's flags. +constexpr uint64_t knownExportFlagBits = EXPORT_SYMBOL_FLAGS_KIND_MASK + | EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION + | EXPORT_SYMBOL_FLAGS_REEXPORT + | EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER + | EXPORT_SYMBOL_FLAGS_STATIC_RESOLVER; + +Expected ExportsTrie::lookUp(std::span trie, std::string_view name) +{ + size_t nodeOffset = 0; + std::string_view remaining = name; + + // The only way around this loop is by matching an edge, which consumes at least + // one character of the `remaining` name we're searching for. Because empty edges + // are rejected below, the walk is bounded by the length of the name no matter + // what the trie's child offsets say, and cannot be made to revisit a node forever. + while (nodeOffset < trie.size()) { + ByteParser node(trie, nodeOffset); + + // A terminal node in a dyld exports trie is: a length, then flags, then some + // ULEB128s whose meaning depends on the flags. See mach-o/loader.h around lines + // 1488–1499 for details. + auto terminalLength = node.consumeULEB128(); + if (!terminalLength) + return makeUnexpected(Failure::Malformed); + + // terminalLength is a full 64-bit value out of the trie, so it is compared + // against what is left of the trie rather than by forming position + terminalLength, + // which could wrap and pass a direct comparison. The subtraction is safe because + // consumeULEB128 stops at the end of the trie, so the position cannot have passed it. + if (*terminalLength > trie.size() - node.position()) + return makeUnexpected(Failure::Malformed); + size_t childrenPosition = node.position() + *terminalLength; + + if (remaining.empty() && *terminalLength) { + // The payload is read through a parser bounded to the terminal, so that a + // terminal declaring less than the flags and offset it needs cannot be made + // to take the bytes that follow it as its own. + ByteParser terminal(trie.subspan(node.position(), *terminalLength)); + auto flags = terminal.consumeULEB128(); + if (!flags) + return makeUnexpected(Failure::Malformed); + if (*flags & ~knownExportFlagBits) + return makeUnexpected(Failure::Malformed); + if (*flags & EXPORT_SYMBOL_FLAGS_REEXPORT) + return makeUnexpected(Failure::ReExport); + + Export::Kind kind; + switch (*flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) { + case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: + kind = Export::Kind::Regular; + break; + case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: + kind = Export::Kind::Absolute; // the value is the address itself. + break; + default: + // Thread-local, or a kind postdating this code. A thread-local's value + // is the offset of its TLV descriptor, not of the variable, and the + // variable's address differs per thread, so there is no one answer to + // report. Saying nothing beats reporting the descriptor as if it were + // the variable. + return makeUnexpected(Failure::UnsupportedKind); + } + + auto value = terminal.consumeULEB128(); + if (!value) + return makeUnexpected(Failure::Malformed); + return Export { kind, *value }; + } + + ByteParser children(trie, childrenPosition); + auto childCount = children.consumeByte(); + if (!childCount) + return makeUnexpected(Failure::Malformed); + + std::optional nextNodeOffset; + for (uint8_t i = 0; i < *childCount; ++i) { + auto edge = children.consumeCString(); + if (!edge) + return makeUnexpected(Failure::Malformed); + // An edge carries the characters that tell a node's children apart, + // so an empty one is malformed. It would also match anything, and + // descending on it would consume none of the name. + if (edge->empty()) + return makeUnexpected(Failure::Malformed); + auto childOffset = children.consumeULEB128(); + if (!childOffset) + return makeUnexpected(Failure::Malformed); + if (remaining.starts_with(*edge)) { + remaining.remove_prefix(edge->size()); + nextNodeOffset = childOffset; + break; + } + } + // No edge matched what is left of the name, so nothing below this node + // can hold it. A node with no children ends the walk the same way. + if (!nextNodeOffset) + return makeUnexpected(Failure::Absent); + nodeOffset = *nextNodeOffset; + } + // A child offset led to or past the end of the trie. + return makeUnexpected(Failure::Malformed); +} + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseExportsTrie.h b/Source/JavaScriptCore/corpse/CorpseExportsTrie.h new file mode 100644 index 000000000000..f86d4bec637f --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseExportsTrie.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include + +namespace JSC { +namespace Corpse { + +// The dyld exports trie of one Mach-O image: a prefix tree over exported symbol +// names, whose terminals say how to compute each symbol's address. +// +// A trie read out of a corpse is untrusted input, so the walk is bounded and a +// malformed encoding is reported rather than guessed at. +class ExportsTrie { +public: + // A matched terminal, and how to turn it into an address. + struct Export { + enum class Kind : uint8_t { + Regular, // An offset from the image's base address. + Absolute, // Already an address, not relative to the image. + }; + Kind kind { Kind::Regular }; + uint64_t value { 0 }; + }; + + enum class Failure : uint8_t { + Absent, + Malformed, // An encoding did not decode, or an offset led outside the trie. + ReExport, // Matched, but the symbol is defined in another image. + UnsupportedKind, // Matched, but the kind has no one address, such as a thread-local. + }; + + static Expected lookUp(std::span trie, std::string_view name); +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseProcess.cpp b/Source/JavaScriptCore/corpse/CorpseProcess.cpp new file mode 100644 index 000000000000..d980d94701c0 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseProcess.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseProcess.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "CorpseError.h" + +#include +#include +#include +#include +#include +#include +#include + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN + +namespace JSC { +namespace Corpse { + +// A task port name outlives the task it named: when the target exits, the right we +// hold becomes a dead name while the name itself is unchanged. MACH_PORT_VALID only +// looks at the name, so it keeps reporting the port as good. Asking the kernel which +// pid the port names is what tells a still-attached process apart from one that has +// since exited -- and, because the answer is compared against m_pid, from a later +// process that inherited the same pid. +bool Process::holdsLiveTask() const +{ + if (!MACH_PORT_VALID(m_taskPort)) + return false; + int pid = -1; + return pid_for_task(m_taskPort, &pid) == KERN_SUCCESS && pid == m_pid; +} + +bool Process::isTranslated() const +{ + struct kinfo_proc info; + size_t length = sizeof info; + int selector[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, m_pid }; + // A pid that no longer exists is not an error here: sysctl succeeds and reports + // that it wrote nothing, so the size has to be checked rather than the result. + if (sysctl(selector, 4, &info, &length, nullptr, 0) || length < sizeof info) + return false; + return info.kp_proc.p_flag & P_TRANSLATED; +} + +bool Process::attach() +{ + if (isAttached()) { + if (holdsLiveTask()) + return true; + // The target exited while we held its port. + detach(); + } + + mach_port_t taskPort = MACH_PORT_NULL; + kern_return_t kr = task_for_pid(mach_task_self(), m_pid, &taskPort); + if (kr == KERN_SUCCESS) { + m_taskPort = taskPort; + return true; + } + + if (kill(m_pid, 0) && errno == ESRCH) + Error::report("No process with PID %d", static_cast(m_pid)); + else { + Error::report("Could not attach to PID %u: %s (0x%x) -- may need to run as root " + "or add the appropriate debugger entitlement", + static_cast(m_pid), mach_error_string(kr), kr); + } + return false; +} + +void Process::detach() +{ + if (MACH_PORT_VALID(m_taskPort)) + mach_port_deallocate(mach_task_self(), m_taskPort); + m_taskPort = MACH_PORT_NULL; +} + +} // namespace Corpse +} // namespace JSC + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_END + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseProcess.h b/Source/JavaScriptCore/corpse/CorpseProcess.h new file mode 100644 index 000000000000..83df1bda88e1 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseProcess.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include + +namespace JSC { +namespace Corpse { + +// Represents a target corpse process identified by PID. It manages the Mach task +// port for that process: attach() acquires it, detach() releases it (but keeps the +// PID so the same Process can be reattached later). +class Process final : public RefCounted { +public: + static Ref create(pid_t pid) { return adoptRef(*new Process(pid)); } + + ~Process() { detach(); } + + bool attach(); + void detach(); + + pid_t pid() const { return m_pid; } + mach_port_t taskPort() const { return m_taskPort; } + + bool isAttached() const { return MACH_PORT_VALID(m_taskPort); } + + // The target process may have terminated while we still hold the port. + bool holdsLiveTask() const; + + // True if the target runs under Rosetta translation. Such a process executes as + // arm64 whatever its own architecture is, so its thread state describes the + // translator rather than the program, and cannot be read as the program's. + bool isTranslated() const; + +private: + explicit Process(pid_t pid) + : m_pid(pid) + { + RELEASE_ASSERT(pid > 0); + } + + pid_t m_pid; + mach_port_t m_taskPort { MACH_PORT_NULL }; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseRegion.cpp b/Source/JavaScriptCore/corpse/CorpseRegion.cpp new file mode 100644 index 000000000000..7d912d228ca3 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseRegion.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseRegion.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include + +namespace JSC { +namespace Corpse { + +uint64_t Region::pageCount() const +{ + return vm_kernel_page_size ? m_size / vm_kernel_page_size : 0; +} + +std::optional Region::findContaining(mach_port_t task, Address address) +{ + // mach_vm_region_recurse reports the region at or above the address it is given, + // so the result only describes `address` if it turns out to contain it. + mach_vm_address_t regionAddress = 0; + mach_vm_size_t regionSize = 0; + vm_region_submap_info_data_64_t info; + for (natural_t depth = 0; ; ++depth) { + regionAddress = address.toMachVMAddress(); + regionSize = 0; + natural_t depthLimit = depth; // We tell the kernel how deep we want to go. Kernel tells us how deep it can go. + mach_msg_type_number_t infoCount = VM_REGION_SUBMAP_INFO_COUNT_64; + kern_return_t kr = mach_vm_region_recurse(task, ®ionAddress, ®ionSize, + &depthLimit, reinterpret_cast(&info), &infoCount); + if (kr != KERN_SUCCESS) + return std::nullopt; + if (!info.is_submap) + break; + } + + Region region; + region.m_base = Address(regionAddress); + region.m_size = static_cast(regionSize); + if (!region.contains(address)) + return std::nullopt; + + region.m_residentPageCount = info.pages_resident; + region.m_dirtyPageCount = info.pages_dirtied; + return region; +} + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseRegion.h b/Source/JavaScriptCore/corpse/CorpseRegion.h new file mode 100644 index 000000000000..d2bfcaa0adb6 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseRegion.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include + +namespace JSC { +namespace Corpse { + +// One mapped region of a task's address space, as the kernel describes it. +class Region { +public: + // The region containing `address`, or nullopt if not found in any region. + static std::optional findContaining(mach_port_t task, Address); + + Address base() const { return m_base; } + size_t size() const { return m_size; } + Address end() const { return m_base + m_size; } + bool contains(Address address) const { return address >= m_base && address < end(); } + + uint64_t pageCount() const; + uint64_t residentPageCount() const { return m_residentPageCount; } + uint64_t dirtyPageCount() const { return m_dirtyPageCount; } + +private: + Address m_base; + size_t m_size { 0 }; + uint64_t m_residentPageCount { 0 }; + uint64_t m_dirtyPageCount { 0 }; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseSnapshot.cpp b/Source/JavaScriptCore/corpse/CorpseSnapshot.cpp new file mode 100644 index 000000000000..14107ca4c9c5 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseSnapshot.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseSnapshot.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "CorpseError.h" + +#include +#include +#include + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN + +namespace JSC { +namespace Corpse { + +WTF_MAKE_TZONE_ALLOCATED_IMPL(Snapshot); + +unsigned Snapshot::s_nextId = 1; + +Snapshot::Snapshot(RefPtr process) + : m_process(WTF::move(process)) + , m_id(s_nextId++) +{ + if (!m_process || !m_process->isAttached()) + return; + + // Snapshot the target into a corpse; only a read port is required from here + // on, and the corpse is independent of the live target. + kern_return_t kr = task_generate_corpse(m_process->taskPort(), &m_corpsePort); + if (kr != KERN_SUCCESS) { + m_corpsePort = MACH_PORT_NULL; + if (!m_process->holdsLiveTask()) { + Error::report("Could not snapshot PID %d: the process has terminated", + static_cast(m_process->pid())); + } else { + Error::report("Could not snapshot PID %d: %s (0x%x)", + static_cast(m_process->pid()), mach_error_string(kr), kr); + } + } +} + +Snapshot::~Snapshot() +{ + if (isValid()) + mach_port_deallocate(mach_task_self(), m_corpsePort); +} + +const Vector& Snapshot::threads() +{ + if (!m_threads) + m_threads = Thread::collect(*this); + return *m_threads; +} + +Address Snapshot::symbol(const char* name) +{ + if (!name || !*name) + return { }; + + auto entry = m_symbols.ensure(StringView::fromLatin1(name), [&] { + return WTF::makeUnique(*this, name); + }); + + return entry.iterator->value->address(); +} + +} // namespace Corpse +} // namespace JSC + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_END + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseSnapshot.h b/Source/JavaScriptCore/corpse/CorpseSnapshot.h new file mode 100644 index 000000000000..3013bf605022 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseSnapshot.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JSC { +namespace Corpse { + +// Owns a corpse (a read-only Mach snapshot of a process) +// Check isValid() to see whether acquisition succeeded. +// +// Snapshots are linked into a DoublyLinkedList by their owner. The list is +// intrusive and does not own its nodes: whoever appends a Snapshot must remove +// it from the list before destroying it. +class Snapshot : public DoublyLinkedListNode { + WTF_MAKE_TZONE_ALLOCATED(Snapshot); +public: + explicit Snapshot(RefPtr); + ~Snapshot(); + + Snapshot(const Snapshot&) = delete; + Snapshot& operator=(const Snapshot&) = delete; + Snapshot(Snapshot&& other) = delete; + + bool isValid() const { return MACH_PORT_VALID(m_corpsePort); } + + // A monotonically increasing identifier assigned at construction. IDs are + // never reused, so they stay stable as snapshots are added and removed. + unsigned id() const { return m_id; } + + Process* process() const { return m_process.get(); } + mach_port_t corpsePort() const { return m_corpsePort; } + + // The threads captured in this corpse, read and cached on the first call. + const Vector& threads(); + + // The address of `name` in this corpse, null if it is not there. + Address symbol(const char* name); + +private: + static unsigned s_nextId; + + RefPtr m_process; + mach_port_t m_corpsePort { MACH_PORT_NULL }; + unsigned m_id; + + std::optional> m_threads; + HashMap> m_symbols; + + Snapshot* m_prev { nullptr }; // Required by DoublyLinkedListNode. + Snapshot* m_next { nullptr }; // Required by DoublyLinkedListNode. + + friend class WTF::DoublyLinkedListNode; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseSymbol.cpp b/Source/JavaScriptCore/corpse/CorpseSymbol.cpp new file mode 100644 index 000000000000..a968dfa30a1b --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseSymbol.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseSymbol.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "CorpseError.h" +#include "CorpseExportsTrie.h" +#include "CorpseSnapshot.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CORPSE_SYMBOL_LOOKUP_DIAGNOSTICS +#define CORPSE_DIAGNOSTIC_DO(statement) statement +#else +#define CORPSE_DIAGNOSTIC_DO(statement) ((void)0) +#endif + +namespace JSC { +namespace Corpse { + +WTF_MAKE_TZONE_ALLOCATED_IMPL(Symbol); + +// It is assumed that this corpse analysis library is built with the same SDK targeting +// the same OS that the corpse binary is built for. While the corpse gives us the data +// to inspect, it does not provide the format. Hence, we need to rely on the invariant +// that this analysis library is built with the same understanding of the same data +// format used in the corpse. This is how we can walk and interpret the corpse's +// dyld exports trie and get addresses of symbols. + +namespace { + +// Sizes and counts read out of a corpse are used to bound loops and to size +// allocations, so they are checked against these limits first. Each one is a +// sanity check on a single value: it says the struct we read was not what we +// thought it was, in which case the addresses in it are not worth chasing. They +// are not a bound on the work a lookup can do, because the per-image limits +// multiply by the image count. maxTotalBytesRead below is that bound. +// +// The values sit above what was empirically measured: across every Mach-O image +// installed on a sample system the largest load commands were 7.4 KB and the +// largest exports trie 2.1 MB, and a process that dlopens every framework on the +// system reaches about 2,800 images. +constexpr size_t maxLoadCommandsSize = 128 * KB; // About 17× the measured maximum. +constexpr size_t maxExportsTrieSize = 16 * MB; // About 8× the measured maximum. +constexpr uint32_t maxImageCount = 16 * 1024; // About 6× the measured maximum. + +// A lookup that finds nothing will read every image's load commands and exports +// trie, which measured 101 MB for the ~2,800 image process above and 0.4 MB for +// a small one. This caps the total for one lookup, so a corpse claiming many +// large images cannot turn a single symbol lookup into unbounded copying. +constexpr size_t maxTotalBytesRead = 256 * MB; // About 2.5× the measured maximum. + +// Copies data from a corpse task's virtual address space. +// FIXME: This is a temporary "get things to work solution", and will be replaced with +// a more efficient memory access from a page manager later that eliminates copying. +class TaskMemory { +public: + explicit TaskMemory(mach_port_t task) + : m_task(task) + { + } + + template + std::optional read(Address address) const + { + static_assert(std::is_trivially_copyable_v); + T out; + if (!readRaw(address, &out, sizeof out)) + return std::nullopt; + return out; + } + + std::optional> readBytes(Address address, size_t length) const + { + Vector buffer; + // Callers derive `length` from the corpse, so failing to allocate is a + // potential outcome here due to potential corruption. + if (!buffer.tryGrow(length)) + return std::nullopt; + if (!readRaw(address, buffer.mutableSpan().data(), length)) + return std::nullopt; + return buffer; + } + +private: + bool readRaw(Address address, void* destination, size_t length) const + { + mach_vm_size_t got = 0; + kern_return_t kr = mach_vm_read_overwrite(m_task, address.toMachVMAddress(), length, + reinterpret_cast(destination), &got); + return kr == KERN_SUCCESS && got == length; + } + + mach_port_t m_task; +}; + +template +std::optional readCommand(std::span commands, size_t offset) +{ + static_assert(std::is_trivially_copyable_v); + // Compared against what is left of the buffer rather than by forming + // offset + sizeof(T), which could wrap and pass a direct comparison. The + // first clause is what makes the subtraction safe. + if (offset > commands.size() || commands.size() - offset < sizeof(T)) + return std::nullopt; + + T value; + memcpySpan(asMutableByteSpan(value), commands.subspan(offset, sizeof(T))); + return value; +} + +bool segmentNameIs(const char (&name)[16], std::string_view expected) +{ + // A segment name fills the whole array when it is exactly 16 characters, in + // which case it has no terminator. + std::span span { name }; + return std::string_view(span.first(strlenSpan(span))) == expected; +} + +} // anonymous namespace + +bool Symbol::hasReadBudget(size_t length) +{ + if (length > m_readBudget) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.readBudgetExhausted); + return false; + } + m_readBudget -= length; + return true; +} + +// Resolves `name` in the one image loaded at `imageAddress`, via its exports +// trie. Returns a null address if this image does not export it. +Address Symbol::resolveInImage(mach_port_t task, Address imageAddress, std::string_view name) +{ + TaskMemory memory(task); + + auto header = memory.read(imageAddress); + if (!header || header->magic != MH_MAGIC_64) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.unreadableHeader); + return { }; + } + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.examined); + if (header->flags & MH_DYLIB_IN_CACHE) + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.inSharedCache); + + if (header->sizeofcmds > maxLoadCommandsSize) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.implausibleCommandsSize); + return { }; + } + if (!hasReadBudget(header->sizeofcmds)) + return { }; + auto commandsBuffer = memory.readBytes(imageAddress + sizeof(mach_header_64), header->sizeofcmds); + if (!commandsBuffer) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.unreadableCommands); + return { }; + } + std::span commands = commandsBuffer->span(); + + std::optional textVMAddress; + std::optional linkeditVMAddress; + std::optional linkeditFileOffset; + std::optional linkeditFileSize; + uint32_t exportOffset = 0; + uint32_t exportSize = 0; + + size_t offset = 0; + for (uint32_t i = 0; i < header->ncmds; ++i) { + auto command = readCommand(commands, offset); + // cmdsize is compared against what is left of the blob rather than by + // forming offset + cmdsize, which could wrap and pass a direct + // comparison. The subtraction is safe only because a successful + // readCommand has already established that offset is within the blob, + // so the clauses have to stay in this order. + if (!command || command->cmdsize < sizeof(load_command) || command->cmdsize > commands.size() - offset) + break; + + // Each case below re-reads `offset` as the larger struct the command + // claims to be. cmdsize has to cover that struct too: a command that + // declares itself smaller is malformed, and reading it anyway would take + // the fields that follow it as its own. + switch (command->cmd) { + case LC_SEGMENT_64: { + if (command->cmdsize < sizeof(segment_command_64)) + break; + auto segment = readCommand(commands, offset); + if (!segment) + break; + if (segmentNameIs(segment->segname, SEG_TEXT)) + textVMAddress = segment->vmaddr; + else if (segmentNameIs(segment->segname, SEG_LINKEDIT)) { + linkeditVMAddress = segment->vmaddr; + linkeditFileOffset = segment->fileoff; + linkeditFileSize = segment->filesize; + } + break; + } + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: { + if (command->cmdsize < sizeof(dyld_info_command)) + break; + auto info = readCommand(commands, offset); + if (!info) + break; + exportOffset = info->export_off; + exportSize = info->export_size; + break; + } + case LC_DYLD_EXPORTS_TRIE: { + if (command->cmdsize < sizeof(linkedit_data_command)) + break; + auto data = readCommand(commands, offset); + if (!data) + break; + exportOffset = data->dataoff; + exportSize = data->datasize; + break; + } + default: + break; + } + offset += command->cmdsize; + } + + if (!textVMAddress || !linkeditVMAddress || !linkeditFileOffset || !linkeditFileSize || !exportSize) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.withoutTrie); + return { }; + } + if (exportSize > maxExportsTrieSize) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.implausibleTrieSize); + return { }; + } + + // The trie is file-backed data living inside __LINKEDIT. So, exportOffset cannot + // be less than the start of __LINKEDIT, cannot exceed the end of __LINKEDIT, and + // the whole trie must fit inside it. + if (exportOffset < *linkeditFileOffset) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.trieOutsideLinkedit); + return { }; + } + uint64_t trieSegmentOffset = exportOffset - *linkeditFileOffset; + if (trieSegmentOffset > *linkeditFileSize || exportSize > *linkeditFileSize - trieSegmentOffset) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.trieOutsideLinkedit); + return { }; + } + + // __TEXT's link-time address against where the image actually landed. The + // load commands give link-time addresses, so everything read out of them + // needs this added to reach the corpse. + uint64_t slide = imageAddress - Address(*textVMAddress); + Address trieAddress = Address(*linkeditVMAddress) + slide + trieSegmentOffset; + + if (!hasReadBudget(exportSize)) + return { }; + auto trieBuffer = memory.readBytes(trieAddress, exportSize); + if (!trieBuffer) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.unreadableTrie); + return { }; + } + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.searched); + + auto found = ExportsTrie::lookUp(trieBuffer->span(), name); + if (!found) { +#if CORPSE_SYMBOL_LOOKUP_DIAGNOSTICS + if (found.error() == ExportsTrie::Failure::ReExport) + ++m_diagnostics.reExports; + else if (found.error() == ExportsTrie::Failure::UnsupportedKind) + ++m_diagnostics.unsupportedKind; +#endif + return { }; + } + if (found->kind == ExportsTrie::Export::Kind::Absolute) + return Address(found->value); + return imageAddress + found->value; +} + +Address Symbol::lookUpName(const Snapshot& snapshot) +{ + if (!snapshot.isValid() || m_name.empty()) + return { }; + + m_readBudget = maxTotalBytesRead; + + auto doLookUp = [&] () -> Address { + std::string name = "_" + m_name; // Use Mach-O symbol name for look up. + + mach_port_t task = snapshot.corpsePort(); + TaskMemory memory(task); + + // dyld publishes the list of loaded images; search each one in turn. + task_dyld_info_data_t dyldInfo; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + if (task_info(task, TASK_DYLD_INFO, reinterpret_cast(&dyldInfo), &count) != KERN_SUCCESS) + return { }; + CORPSE_DIAGNOSTIC_DO(m_diagnostics.readDyldInfo = true); + + Address allImageInfosAddress { dyldInfo.all_image_info_addr }; + CORPSE_DIAGNOSTIC_DO(m_diagnostics.allImageInfosAddress = allImageInfosAddress); + if (!allImageInfosAddress) + return { }; + + auto allImages = memory.read(allImageInfosAddress); + if (!allImages) + return { }; + CORPSE_DIAGNOSTIC_DO(m_diagnostics.readAllImageInfos = true); + CORPSE_DIAGNOSTIC_DO(m_diagnostics.version = allImages->version); + + Address rawArrayAddress { allImages->infoArray }; + Address arrayAddress = rawArrayAddress.stripped(); + uint32_t imageCount = allImages->infoArrayCount; + CORPSE_DIAGNOSTIC_DO(m_diagnostics.rawImageArrayAddress = rawArrayAddress); + CORPSE_DIAGNOSTIC_DO(m_diagnostics.imageArrayAddress = arrayAddress); + CORPSE_DIAGNOSTIC_DO(m_diagnostics.images = imageCount); + if (!arrayAddress || !imageCount) + return { }; + // Each image below costs a Mach round-trip and two buffer reads, so an + // implausible count is a lot of work to be talked into doing. + if (imageCount > maxImageCount) { + CORPSE_DIAGNOSTIC_DO(m_diagnostics.implausibleImageCount = true); + return { }; + } + + for (uint32_t i = 0; i < imageCount; ++i) { + auto info = memory.read(arrayAddress + static_cast(i) * sizeof(dyld_image_info)); + if (!info) { + CORPSE_DIAGNOSTIC_DO(++m_diagnostics.unreadableInfo); + continue; + } + auto imageAddress = Address(info->imageLoadAddress).stripped(); + auto symbolAddress = resolveInImage(task, imageAddress, name); + if (symbolAddress) + return symbolAddress; + } + + return { }; + }; + + Address address = doLookUp(); +#if CORPSE_SYMBOL_LOOKUP_DIAGNOSTICS + if (!address) + reportFailure(snapshot); +#endif + return address; +} + +#if CORPSE_SYMBOL_LOOKUP_DIAGNOSTICS + +// Says how a failed search went, so a caller can tell "the symbol is not +// exported" from "the corpse could not be read". +void Symbol::reportFailure(const Snapshot& snapshot) const +{ + const Diagnostics& d = m_diagnostics; + + Error::report("No symbol '_%s' in pid %d", m_name.c_str(), + static_cast(snapshot.process()->pid())); + + if (!d.readDyldInfo) { + Error::report(" could not read dyld information (task_info TASK_DYLD_INFO failed)"); + return; + } + if (!d.allImageInfosAddress) { + Error::report(" dyld reports no image list (all_image_info_addr is 0)"); + return; + } + if (!d.readAllImageInfos) { + Error::report(" could not read dyld_all_image_infos at 0x%llx", + d.allImageInfosAddress.toMachVMAddress()); + return; + } + + // A sane version says the struct read probably landed on real data, which is what + // makes the image count and array address below worth printing. + Error::report(" dyld_all_image_infos v%u at 0x%llx lists %u images at 0x%llx", + d.version, d.allImageInfosAddress.toMachVMAddress(), + d.images, d.imageArrayAddress.toMachVMAddress()); + if (d.rawImageArrayAddress != d.imageArrayAddress) { + Error::report(" that address was ptrauth-signed as 0x%llx; the signature was stripped", + d.rawImageArrayAddress.toMachVMAddress()); + } + if (!d.imageArrayAddress || !d.images) { + Error::report(" that list is empty, so there was nothing to search"); + return; + } + if (d.implausibleImageCount) { + Error::report(" that count is too large to be a real image list, so the struct read" + " was not dyld_all_image_infos and was not searched"); + return; + } + + Error::report(" read %u of %u image headers (%u in the shared cache), walked %u exports tries", + d.examined, d.images, d.inSharedCache, d.searched); + if (d.unreadableInfo) + Error::report(" %u image list entries were unreadable", d.unreadableInfo); + if (d.unreadableHeader) + Error::report(" %u image headers were unreadable or not 64-bit Mach-O", d.unreadableHeader); + if (d.implausibleCommandsSize) + Error::report(" %u images had implausible load command sizes", d.implausibleCommandsSize); + if (d.unreadableCommands) + Error::report(" %u images had unreadable load commands", d.unreadableCommands); + if (d.withoutTrie) + Error::report(" %u images had no exports trie", d.withoutTrie); + if (d.implausibleTrieSize) + Error::report(" %u images had implausible exports trie sizes", d.implausibleTrieSize); + if (d.trieOutsideLinkedit) + Error::report(" %u images placed their exports trie outside __LINKEDIT", d.trieOutsideLinkedit); + if (d.unreadableTrie) + Error::report(" %u exports tries were not readable", d.unreadableTrie); + if (d.readBudgetExhausted) { + Error::report(" gave up after reading %u MB from the corpse; %u images were skipped", + static_cast(maxTotalBytesRead / MB), d.readBudgetExhausted); + } + if (d.reExports) + Error::report(" %u images re-export the name from elsewhere, which is not followed", d.reExports); + if (d.unsupportedKind) + Error::report(" %u images export the name in a form that has no single address, such as a thread-local", d.unsupportedKind); + + if (d.searched) { + Error::report( + " only exported symbols appear in an exports trie: a symbol hidden" + " by the linker is invisible here even though lldb can still find" + " it in the symbol table"); + } else { + Error::report( + " no trie was searched, so this is a memory-access problem rather" + " than the symbol being absent"); + } +} + +#endif // CORPSE_SYMBOL_LOOKUP_DIAGNOSTICS + +Symbol::Symbol(const Snapshot& snapshot, const char* name) + : m_name(name ? name : "") +{ + if (!m_name.empty()) + m_address = lookUpName(snapshot); +} + +} // namespace Corpse +} // namespace JSC + +#undef CORPSE_DIAGNOSTIC_DO + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseSymbol.h b/Source/JavaScriptCore/corpse/CorpseSymbol.h new file mode 100644 index 000000000000..e32f53d98e09 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseSymbol.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include +#include + +// Enable for more detailed error messages on what may have caused a symbol lookup failure. +#define CORPSE_SYMBOL_LOOKUP_DIAGNOSTICS 0 + +namespace JSC { +namespace Corpse { + +class Snapshot; + +// A symbol looked up in a corpse by name. The lookup happens on construction. +// +// Only regular and absolute exports are read out of an image's trie. A re-export is +// skipped rather than followed, so a name that one image re-exports resolves in the +// image that defines it, as long as that image is loaded in the corpse. A +// thread-local is not found at all. +// +// A re-export may also rename, and then no image exports the name at all: memcpy +// exists only as libsystem_c's re-export of __platform_memmove from +// libsystem_platform, so a lookup of memcpy finds nothing while a lookup of +// __platform_memmove succeeds. +class Symbol { + WTF_MAKE_TZONE_ALLOCATED(Symbol); +public: + Symbol(const Snapshot&, const char* name); + + const std::string& name() const { return m_name; } + + Address address() const { return m_address; } // Null means not found. + bool isValid() const { return static_cast(m_address); } + +private: + Address lookUpName(const Snapshot&); + Address resolveInImage(mach_port_t, Address loadAddress, std::string_view name); + bool hasReadBudget(size_t length); + +#if CORPSE_SYMBOL_LOOKUP_DIAGNOSTICS + // How far a search got, so a failure can name the stage that fell short. + struct Diagnostics { + bool readDyldInfo { false }; + Address allImageInfosAddress; + bool readAllImageInfos { false }; + uint32_t version { 0 }; // dyld_all_image_infos::version. + Address rawImageArrayAddress; // As stored, possibly signed. + Address imageArrayAddress; // ...with any signature stripped. + unsigned images { 0 }; // Images dyld reported. + bool implausibleImageCount { false }; // ...but too many to be believed. + unsigned examined { 0 }; // ...whose Mach header we read. + unsigned inSharedCache { 0 }; // ...of those, in the shared cache. + unsigned unreadableInfo { 0 }; // dyld_image_info unreadable. + unsigned unreadableHeader { 0 }; // Header missing or not 64-bit. + unsigned implausibleCommandsSize { 0 }; // sizeofcmds too large to believe. + unsigned unreadableCommands { 0 }; // Load commands unreadable. + unsigned withoutTrie { 0 }; // No trie, or no __TEXT/__LINKEDIT. + unsigned implausibleTrieSize { 0 }; // Trie size too large to believe. + unsigned trieOutsideLinkedit { 0 }; // Trie not within __LINKEDIT. + unsigned unreadableTrie { 0 }; // Trie located but not readable. + unsigned readBudgetExhausted { 0 }; // Gave up: the lookup hit its read budget. + unsigned searched { 0 }; // Tries actually walked. + unsigned reExports { 0 }; // Matched, but re-exported. + unsigned unsupportedKind { 0 }; // Matched, but not an export kind with one address. + }; + + void reportFailure(const Snapshot&) const; + + Diagnostics m_diagnostics; +#endif + + std::string m_name; + Address m_address; + + // What this lookup may still copy out of the corpse. Set when the search starts. + size_t m_readBudget { 0 }; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseThread.cpp b/Source/JavaScriptCore/corpse/CorpseThread.cpp new file mode 100644 index 000000000000..f70ee5151c61 --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseThread.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseThread.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "CorpseError.h" +#include "CorpseProcess.h" +#include "CorpseRegion.h" +#include "CorpseSnapshot.h" + +#include +#include +#include +#include +#include +#include +#include + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN + +namespace JSC { +namespace Corpse { + +static std::optional
readStackPointer(thread_act_t thread) +{ +#if CPU(ARM64) + arm_thread_state64_t state = { }; + mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; + if (thread_get_state(thread, ARM_THREAD_STATE64, reinterpret_cast(&state), &count) != KERN_SUCCESS) + return std::nullopt; // May fail e.g. for Rosetta. + // The target's pc/lr/sp/fp arrive signed for its own ptrauth context. On an + // arm64e build the accessors would try to authenticate them against ours and + // trap (EXC_BAD_ACCESS / EXC_ARM_PAC_FAIL), so strip the signatures first. + // Stripping also marks the state as unsigned, so the accessor reads it raw. + arm_thread_state64_ptrauth_strip(state); + return Address(arm_thread_state64_get_sp(state)); +#elif CPU(X86_64) + x86_thread_state64_t state = { }; + mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; + if (thread_get_state(thread, x86_THREAD_STATE64, reinterpret_cast(&state), &count) != KERN_SUCCESS) + return std::nullopt; + return Address(state.__rsp); +#else + UNUSED_PARAM(thread); + return std::nullopt; +#endif +} + +const char* Thread::runStateDescription() const +{ + switch (m_runState) { + case TH_STATE_RUNNING: + return "running"; + case TH_STATE_STOPPED: + return "stopped"; + case TH_STATE_WAITING: + return "waiting"; + case TH_STATE_UNINTERRUPTIBLE: + return "uninterruptible"; + case TH_STATE_HALTED: + return "halted"; + default: + return "unknown"; + } +} + +Vector Thread::collect(const Snapshot& snapshot) +{ + Vector result; + + if (!snapshot.isValid()) { + Error::report("Cannot read threads from an invalid snapshot"); + return result; + } + mach_port_t task = snapshot.corpsePort(); + + thread_act_array_t threads = nullptr; + mach_msg_type_number_t threadCount = 0; + kern_return_t kr = task_threads(task, &threads, &threadCount); + if (kr != KERN_SUCCESS) { + pid_t pid = snapshot.process()->pid(); + Error::report("Could not read the thread list for pid %d: %s (0x%x)", + static_cast(pid), mach_error_string(kr), kr); + return result; + } + + result.reserveCapacity(threadCount); + + // A translated target executes as arm64 whatever its own architecture is, so its + // threads' stack pointers belong to Rosetta's runtime rather than to the program. + // Those addresses do land in real mappings, so reporting the region around one + // would name a plausible but wrong stack; report no stack instead. + Process* process = snapshot.process(); + bool isTranslated = process->isTranslated(); + if (isTranslated) { + Error::report("Thread stacks for pid %d are not available: the process runs" + " under Rosetta translation, whose thread state does not describe the program", + static_cast(process->pid())); + } + + unsigned unreadableStates = 0; + for (mach_msg_type_number_t i = 0; i < threadCount; ++i) { + Thread thread; + + thread_identifier_info_data_t identifierInfo; + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + if (thread_info(threads[i], THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifierInfo), &count) == KERN_SUCCESS) + thread.m_id = identifierInfo.thread_id; + + thread_basic_info_data_t basicInfo; + count = THREAD_BASIC_INFO_COUNT; + if (thread_info(threads[i], THREAD_BASIC_INFO, reinterpret_cast(&basicInfo), &count) == KERN_SUCCESS) { + thread.m_runState = basicInfo.run_state; + thread.m_suspendCount = basicInfo.suspend_count; + thread.m_userTimeUsec = static_cast(basicInfo.user_time.seconds) * 1000000 + + basicInfo.user_time.microseconds; + thread.m_systemTimeUsec = static_cast(basicInfo.system_time.seconds) * 1000000 + + basicInfo.system_time.microseconds; + } + + // Extended info is the only flavor that reports the pthread name. + thread_extended_info_data_t extendedInfo; + count = THREAD_EXTENDED_INFO_COUNT; + if (thread_info(threads[i], THREAD_EXTENDED_INFO, reinterpret_cast(&extendedInfo), &count) == KERN_SUCCESS) { + extendedInfo.pth_name[sizeof(extendedInfo.pth_name) - 1] = '\0'; + thread.m_name = extendedInfo.pth_name; + } + + // The stack is the region the stack pointer points into. + if (!isTranslated) { + if (auto stackPointer = readStackPointer(threads[i])) { + thread.m_stackPointer = *stackPointer; + if (auto region = Region::findContaining(task, thread.m_stackPointer)) + thread.m_stackRegion = *region; + } else + ++unreadableStates; + } + + result.append(thread); + } + + // Failing to read the thread state leaves a thread with no stack, which on its + // own looks the same as a thread that has none. Say which it was. + if (unreadableStates) { + Error::report("Could not read the thread state of %u of %u threads in pid %d:" + " this build cannot read the target's architecture", + unreadableStates, static_cast(threadCount), + static_cast(process->pid())); + } + + // task_threads hands us a right to each thread plus the array itself. + for (mach_msg_type_number_t i = 0; i < threadCount; ++i) + mach_port_deallocate(mach_task_self(), threads[i]); + mach_vm_size_t threadsSize = threadCount * sizeof(thread_act_t); + mach_vm_deallocate(mach_task_self(), reinterpret_cast(threads), threadsSize); + + return result; +} +} // namespace Corpse +} // namespace JSC + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_END + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/CorpseThread.h b/Source/JavaScriptCore/corpse/CorpseThread.h new file mode 100644 index 000000000000..64b2ae2d433f --- /dev/null +++ b/Source/JavaScriptCore/corpse/CorpseThread.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include +#include + +namespace JSC { +namespace Corpse { + +class Snapshot; + +// A snapshot of thread values read out of a corpse. +class Thread { +public: + // The kernel's system-wide unique 64-bit thread id, as reported by lldb and + // spindump. This is an identifier, not an address. + uint64_t id() const { return m_id; } + + // The pthread name, empty if the thread was never named. + const std::string& name() const { return m_name; } + + int runState() const { return m_runState; } + int suspendCount() const { return m_suspendCount; } + uint64_t userTimeUsec() const { return m_userTimeUsec; } + uint64_t systemTimeUsec() const { return m_systemTimeUsec; } + + Address stackPointer() const { return m_stackPointer; } + + const Region& stackRegion() const { return m_stackRegion; } + bool hasStack() const { return m_stackRegion.size(); } + + const char* runStateDescription() const; + +private: + static Vector collect(const Snapshot&); + + uint64_t m_id { 0 }; + std::string m_name; + int m_runState { 0 }; + int m_suspendCount { 0 }; + uint64_t m_userTimeUsec { 0 }; + uint64_t m_systemTimeUsec { 0 }; + Address m_stackPointer; + Region m_stackRegion; + + friend class Snapshot; +}; + +} // namespace Corpse +} // namespace JSC + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseAddressTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseAddressTest.cpp new file mode 100644 index 000000000000..4f3e685bba92 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseAddressTest.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseAddressTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include + +#if CPU(ARM64E) +#include +#endif + +namespace JSCToolsTest { + +using JSC::Corpse::Address; + +void testAddress() +{ + if (!beginSuite("Address")) + return; + + { + Address none; + TEST_ASSERT(!none, "a default Address is null"); + TEST_ASSERT(none == nullptr, "a default Address compares equal to nullptr"); + TEST_ASSERT_HEX_EQ(none.toMachVMAddress(), 0, "a default Address holds zero"); + } + { + Address address(static_cast(0x1000)); + TEST_ASSERT(static_cast(address), "a non-zero Address is not null"); + TEST_ASSERT(!(address == nullptr), "a non-zero Address does not compare equal to nullptr"); + TEST_ASSERT_HEX_EQ(address.toMachVMAddress(), 0x1000, "an Address holds what it was given"); + } + { + int local = 0; + Address address(&local); + TEST_ASSERT_HEX_EQ(address.toMachVMAddress(), reinterpret_cast(&local), + "an Address built from a pointer holds that pointer"); + } + { + // The whole point of the type: a corpse address must not be usable as a + // local one by accident, so there is no conversion out of it. + TEST_ASSERT(!(std::is_convertible_v), + "an Address does not convert to an integer"); + TEST_ASSERT(!(std::is_convertible_v), + "an Address does not convert to a pointer"); + } + { + Address low(static_cast(0x1000)); + Address high(static_cast(0x2000)); + TEST_ASSERT(low < high, "Addresses order by value"); + TEST_ASSERT(high > low, "Addresses order by value the other way"); + TEST_ASSERT(low <= low && low >= low, "an Address is not less or greater than itself"); + TEST_ASSERT(low == Address(static_cast(0x1000)), "equal values compare equal"); + TEST_ASSERT(low != high, "different values do not compare equal"); + } + { + Address base(static_cast(0x1000)); + TEST_ASSERT_HEX_EQ((base + 0x20).toMachVMAddress(), 0x1020, "adding an offset moves forward"); + TEST_ASSERT_HEX_EQ((base - 0x20).toMachVMAddress(), 0x0fe0, "subtracting an offset moves back"); + TEST_ASSERT_HEX_EQ(Address(static_cast(0x1030)) - base, 0x30, + "subtracting two Addresses gives the distance between them"); + } + { + // A plain address has nothing to strip, whatever the platform. + Address plain(static_cast(0x0000000100002000)); + TEST_ASSERT_HEX_EQ(plain.stripped().toMachVMAddress(), 0x0000000100002000, + "stripping an unsigned address changes nothing"); + } +#if CPU(ARM64E) + { + // A pointer read out of a corpse arrives signed for the target's context, + // and must be reduced to the address it names before it is used as one. + void* raw = reinterpret_cast(static_cast(0x0000000100002000)); + void* signedPointer; + unsigned count = 0; + constexpr unsigned maxRetryCount = 10; + do { + signedPointer = ptrauth_sign_unauthenticated(raw, ptrauth_key_process_dependent_code, 0); + } while (signedPointer == raw && ++count <= maxRetryCount); + TEST_ASSERT(count <= maxRetryCount, "unable to generate PAC signed pointer for test"); + TEST_ASSERT_HEX_EQ(Address(signedPointer).stripped().toMachVMAddress(), + reinterpret_cast(raw), "stripping recovers the address a signed pointer names"); + } + { + // Top-byte-ignore and memory tagging both leave data in the top byte, which + // is not part of the address either. + Address tagged(static_cast(0x4200000100002000)); + TEST_ASSERT_HEX_EQ(tagged.stripped().toMachVMAddress(), 0x0000000100002000, + "stripping clears a tagged top byte"); + } +#endif +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseAddressTest.h b/Source/JavaScriptCore/corpse/tests/CorpseAddressTest.h new file mode 100644 index 000000000000..2af264d1993e --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseAddressTest.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSCToolsTest { + +void testAddress(); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.cpp new file mode 100644 index 000000000000..cbc380b92fda --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseByteParserTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include + +namespace JSCToolsTest { + +using JSC::Corpse::ByteParser; + +void testByteParser() +{ + if (!beginSuite("ByteParser")) + return; + + { + Vector empty; + ByteParser parser(empty.span()); + TEST_ASSERT(!parser.consumeByte(), "consumeByte on an empty buffer yields nothing"); + TEST_ASSERT(!parser.consumeULEB128(), "consumeULEB128 on an empty buffer yields nothing"); + TEST_ASSERT(!parser.consumeCString(), "consumeCString on an empty buffer yields nothing"); + TEST_ASSERT_EQ(parser.position(), static_cast(0), "a failed read does not advance"); + } + { + Vector data { 0x11, 0x22 }; + ByteParser parser(data.span()); + auto first = parser.consumeByte(); + TEST_ASSERT(first && *first == 0x11, "consumeByte yields the first byte"); + TEST_ASSERT_EQ(parser.position(), static_cast(1), "consumeByte advances by one"); + auto second = parser.consumeByte(); + TEST_ASSERT(second && *second == 0x22, "consumeByte yields the next byte"); + TEST_ASSERT_EQ(parser.position(), static_cast(2), "consumeByte advances past the last byte"); + TEST_ASSERT(!parser.consumeByte(), "consumeByte stops at the end"); + } + { + // A parser may start part way in, which is how a node is read out of a trie. + Vector data { 0x11, 0x22, 0x33 }; + ByteParser parser(data.span(), 2); + auto value = parser.consumeByte(); + TEST_ASSERT(value && *value == 0x33, "a parser starts at the position it is given"); + TEST_ASSERT_EQ(parser.position(), static_cast(3), "a read advances from the given position"); + } + { + // A trie node's fields are ULEB128s read through a parser bounded to the whole + // trie but starting at the node, so decoding has to begin at that position and + // leave the cursor on the field that follows. + Vector data { 0x11, 0x22, 0xe5, 0x8e, 0x26, 0x33 }; + ByteParser parser(data.span(), 2); + auto value = parser.consumeULEB128(); + TEST_ASSERT(value && *value == 624485, "consumeULEB128 decodes from the position it is given"); + TEST_ASSERT_EQ(parser.position(), static_cast(5), "consumeULEB128 stops after the value it decoded"); + auto next = parser.consumeByte(); + TEST_ASSERT(next && *next == 0x33, "the byte after a decoded value is left for the next read"); + } + { + // A failed read rewinds to where the cursor was, which is not necessarily the + // start of the buffer. + Vector data { 0x11, 0x22, 0x80 }; + ByteParser parser(data.span(), 2); + TEST_ASSERT(!parser.consumeULEB128(), "a truncated ULEB128 is rejected wherever it starts"); + TEST_ASSERT_EQ(parser.position(), static_cast(2), "a failed read rewinds to the given position"); + } + + struct ULEBCase { + Vector bytes; + bool decodes; + uint64_t value; + const char* description; + }; + Vector ulebCases; + ulebCases.append({ { 0x00 }, true, 0, "zero" }); + ulebCases.append({ { 0x01 }, true, 1, "one" }); + ulebCases.append({ { 0x7f }, true, 127, "the largest one-byte value" }); + ulebCases.append({ { 0x80, 0x01 }, true, 128, "the smallest two-byte value" }); + ulebCases.append({ { 0xe5, 0x8e, 0x26 }, true, 624485, "a three-byte value" }); + ulebCases.append({ { 0x80, 0x80, 0x80, 0x00 }, true, 0, "zero padded with continuations" }); + ulebCases.append({ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 }, true, + std::numeric_limits::max(), "the largest 64-bit value" }); + // Rejected: the top group carries bits that do not fit in 64. + ulebCases.append({ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02 }, false, 0, + "a value one bit too wide for 64 bits" }); + // Rejected: the shift would reach 64, which is undefined rather than merely large. + ulebCases.append({ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, false, 0, + "an encoding longer than 64 bits can hold" }); + ulebCases.append({ { 0x80 }, false, 0, "a value whose continuation runs off the end" }); + ulebCases.append({ { 0xff, 0xff }, false, 0, "a truncated multi-byte value" }); + + for (const ULEBCase& testCase : ulebCases) { + ByteParser parser(testCase.bytes.span()); + auto decoded = parser.consumeULEB128(); + if (!testCase.decodes) { + TEST_ASSERT(!decoded, testCase.description); + TEST_ASSERT_EQ(parser.position(), static_cast(0), testCase.description); + continue; + } + if (!decoded) { + TEST_ASSERT(decoded, testCase.description); + continue; + } + TEST_ASSERT_HEX_EQ(*decoded, testCase.value, testCase.description); + TEST_ASSERT_EQ(parser.position(), testCase.bytes.size(), testCase.description); + } + + { + Vector data { 'a', 'b', 0, 'c', 0 }; + ByteParser parser(data.span()); + auto first = parser.consumeCString(); + TEST_ASSERT(first && *first == "ab", "consumeCString yields the string"); + TEST_ASSERT_EQ(parser.position(), static_cast(3), "consumeCString consumes the terminator"); + auto second = parser.consumeCString(); + TEST_ASSERT(second && *second == "c", "consumeCString yields the following string"); + TEST_ASSERT_EQ(parser.position(), static_cast(5), "consumeCString consumes the second terminator"); + TEST_ASSERT(!parser.consumeCString(), "consumeCString stops at the end"); + } + { + Vector data { 0 }; + ByteParser parser(data.span()); + auto empty = parser.consumeCString(); + TEST_ASSERT(empty && empty->empty(), "an empty string is a string"); + TEST_ASSERT_EQ(parser.position(), static_cast(1), "an empty string still consumes its terminator"); + } + { + // A read that fails consumes nothing. + Vector unterminated { 'a', 'b' }; + ByteParser cstring(unterminated.span()); + TEST_ASSERT(!cstring.consumeCString(), "an unterminated string is rejected"); + TEST_ASSERT_EQ(cstring.position(), static_cast(0), + "a failed string read leaves the cursor where it was"); + + Vector truncated { 0x80, 0x80 }; + ByteParser uleb(truncated.span()); + TEST_ASSERT(!uleb.consumeULEB128(), "a truncated ULEB128 is rejected"); + TEST_ASSERT_EQ(uleb.position(), static_cast(0), + "a failed ULEB128 read leaves the cursor where it was"); + + // The failed read should not advance the cursor. Therefore, the 2nd read should succeed. + Vector lone { 0x80 }; + ByteParser retry(lone.span()); + TEST_ASSERT(!retry.consumeULEB128(), "a lone continuation byte is not a value"); + auto byte = retry.consumeByte(); + TEST_ASSERT(byte && *byte == 0x80, "a failed read leaves its bytes for another reader"); + } + { + Vector data { 0x11, 'a', 0 }; + for (size_t position : { data.size(), data.size() + 1, std::numeric_limits::max() }) { + ByteParser parser(data.span(), position); + TEST_ASSERT(!parser.consumeByte(), "consumeByte from beyond the end yields nothing"); + TEST_ASSERT(!parser.consumeULEB128(), "consumeULEB128 from beyond the end yields nothing"); + TEST_ASSERT(!parser.consumeCString(), "consumeCString from beyond the end yields nothing"); + TEST_ASSERT_EQ(parser.position(), position, "a failed read beyond the end does not move the cursor"); + } + } +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.h b/Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.h new file mode 100644 index 000000000000..02c3681aee74 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseByteParserTest.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSCToolsTest { + +void testByteParser(); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.cpp new file mode 100644 index 000000000000..c13efc1b801d --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.cpp @@ -0,0 +1,811 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseExportsTrieTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JSCToolsTest { + +using JSC::Corpse::ExportsTrie; + +namespace { + +// Builds the bytes of a dyld exports trie, or of something that is not quite one. +// Emitting bytes rather than describing exports is deliberate: most of what is +// worth testing here is malformed, and could not be described any other way. +class TrieBytes { +public: + void byte(uint8_t value) { m_bytes.append(value); } + void bytes(std::span); + void uleb128(uint64_t); + + // A ULEB128 padded to a fixed width so that a forward reference can be + // patched once its target is known. Non-canonical but well formed: the + // padding bytes carry a continuation bit and no payload. + static constexpr unsigned fixedWidth = 3; + size_t uleb128Fixed(uint64_t); + void patchUleb128Fixed(size_t position, uint64_t); + + void cString(std::string_view); // With its terminator. + void string(std::string_view); // Without. + + size_t position() const { return m_bytes.size(); } + std::span span() const { return m_bytes.span(); } + Vector take() { return WTF::move(m_bytes); } + +private: + Vector m_bytes; +}; + +// The flags and payload of one terminal, as a trie encodes them. +struct TerminalSpec { + uint64_t flags { 0 }; + // The ULEB128s that follow the flags. What they mean depends on the flags: + // one offset for an ordinary export, a stub offset then a resolver offset + // for a stub-and-resolver, an ordinal for a re-export. + Vector values; + // Appended after the values, for a re-export's imported name. + std::string_view trailingString; + bool hasTrailingString { false }; +}; + +// A trie holding one export named "", so that a look up of "" reaches the +// terminal without walking any edge. Isolates terminal decoding from the walk. +Vector terminalOnlyTrie(const TerminalSpec&); + +// A trie whose root has one edge, `name`, leading to a terminal. +Vector singleExportTrie(std::string_view name, const TerminalSpec&); + +// A trie spelling one export across several edges, so a look up has to walk. +Vector chainedExportTrie(std::span edges, const TerminalSpec&); + +// An ordinary export at `offset` from the image's base. +TerminalSpec regularExport(uint64_t offset); + +void TrieBytes::bytes(std::span data) +{ + m_bytes.append(data); +} + +void TrieBytes::uleb128(uint64_t value) +{ + do { + uint8_t group = value & 0x7f; + value >>= 7; + if (value) + group |= 0x80; + m_bytes.append(group); + } while (value); +} + +size_t TrieBytes::uleb128Fixed(uint64_t value) +{ + size_t start = m_bytes.size(); + for (unsigned i = 0; i < fixedWidth; ++i) { + uint8_t group = (value >> (7 * i)) & 0x7f; + if (i + 1 < fixedWidth) + group |= 0x80; + m_bytes.append(group); + } + return start; +} + +void TrieBytes::patchUleb128Fixed(size_t position, uint64_t value) +{ + for (unsigned i = 0; i < fixedWidth; ++i) { + uint8_t group = (value >> (7 * i)) & 0x7f; + if (i + 1 < fixedWidth) + group |= 0x80; + m_bytes[position + i] = group; + } +} + +void TrieBytes::cString(std::string_view text) +{ + string(text); + m_bytes.append(0); +} + +void TrieBytes::string(std::string_view text) +{ + for (char character : text) + m_bytes.append(static_cast(character)); +} + +TerminalSpec regularExport(uint64_t offset) +{ + TerminalSpec spec; + spec.values.append(offset); + return spec; +} + +// The bytes a terminal node holds after its length: the flags, then whatever +// ULEB128s and string the flags call for. +static Vector terminalPayload(const TerminalSpec& spec) +{ + TrieBytes payload; + payload.uleb128(spec.flags); + for (uint64_t value : spec.values) + payload.uleb128(value); + if (spec.hasTrailingString) + payload.cString(spec.trailingString); + return payload.take(); +} + +// A node with a terminal and no children. +static void appendTerminalNode(TrieBytes& trie, const TerminalSpec& spec) +{ + Vector payload = terminalPayload(spec); + trie.uleb128(payload.size()); + trie.bytes(payload.span()); + trie.byte(0); // No children. +} + +Vector terminalOnlyTrie(const TerminalSpec& spec) +{ + TrieBytes trie; + appendTerminalNode(trie, spec); + return trie.take(); +} + +Vector chainedExportTrie(std::span edges, const TerminalSpec& spec) +{ + TrieBytes trie; + + // Every node but the last points at the one after it, whose position is not + // known until it has been emitted, so each reference is patched from behind. + Vector patchPositions; + for (std::string_view edge : edges) { + if (!patchPositions.isEmpty()) { + trie.patchUleb128Fixed(patchPositions.last(), trie.position()); + patchPositions.removeLast(); + } + trie.uleb128(0); // No terminal on the way down. + trie.byte(1); // One edge. + trie.cString(edge); + patchPositions.append(trie.uleb128Fixed(0)); + } + if (!patchPositions.isEmpty()) + trie.patchUleb128Fixed(patchPositions.last(), trie.position()); + appendTerminalNode(trie, spec); + return trie.take(); +} + +Vector singleExportTrie(std::string_view name, const TerminalSpec& spec) +{ + std::array edges { name }; + return chainedExportTrie(std::span(edges), spec); +} + +// A node's children count is one byte, so 255 edges is as wide as a node can be. +static constexpr unsigned maximumEdgeCount = 255; + +// The name of the `index`th edge of a fan-out trie. Two characters wide so that no +// edge is a prefix of another, which leaves exactly one of them matching a name. +static std::string fanOutEdgeName(unsigned index) +{ + return { static_cast('a' + index / 16), static_cast('a' + index % 16) }; +} + +// Distinct per edge, so that a walk landing on the wrong child is caught. +static constexpr uint64_t fanOutExportOffset(unsigned index) +{ + return 0x1000 + index; +} + +// A root with `edgeCount` edges, each leading to a terminal of its own. +static Vector fanOutTrie(unsigned edgeCount) +{ + TrieBytes trie; + trie.uleb128(0); // The root itself exports nothing. + trie.byte(static_cast(edgeCount)); + + // Each edge points at a node that has not been emitted yet, so the references + // are patched once their targets are laid down below. + Vector patchPositions; + for (unsigned index = 0; index < edgeCount; ++index) { + trie.cString(fanOutEdgeName(index)); + patchPositions.append(trie.uleb128Fixed(0)); + } + for (unsigned index = 0; index < edgeCount; ++index) { + trie.patchUleb128Fixed(patchPositions[index], trie.position()); + appendTerminalNode(trie, regularExport(fanOutExportOffset(index))); + } + return trie.take(); +} + +using Failure = ExportsTrie::Failure; +using Kind = ExportsTrie::Export::Kind; + +} // anonymous namespace + +// A node carrying both a terminal and one edge, which is how a trie holds an +// export whose name is a prefix of another export's name. +static Vector prefixAndChildTrie(std::string_view rootEdge, const TerminalSpec& atRootEdge, + std::string_view childEdge, const TerminalSpec& atChild) +{ + TrieBytes trie; + trie.uleb128(0); // The root itself exports nothing. + trie.byte(1); + trie.cString(rootEdge); + size_t rootEdgePatch = trie.uleb128Fixed(0); + + trie.patchUleb128Fixed(rootEdgePatch, trie.position()); + TrieBytes payload; + payload.uleb128(atRootEdge.flags); + for (uint64_t value : atRootEdge.values) + payload.uleb128(value); + Vector payloadBytes = payload.take(); + trie.uleb128(payloadBytes.size()); + trie.bytes(payloadBytes.span()); + trie.byte(1); + trie.cString(childEdge); + size_t childPatch = trie.uleb128Fixed(0); + + trie.patchUleb128Fixed(childPatch, trie.position()); + TrieBytes childPayload; + childPayload.uleb128(atChild.flags); + for (uint64_t value : atChild.values) + childPayload.uleb128(value); + Vector childPayloadBytes = childPayload.take(); + trie.uleb128(childPayloadBytes.size()); + trie.bytes(childPayloadBytes.span()); + trie.byte(0); + + return trie.take(); +} + +static void testFoundExports() +{ + { + Vector trie = singleExportTrie("_foo", regularExport(0x1234)); + auto found = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(found, "an exported name is found"); + if (found) { + TEST_ASSERT(found->kind == Kind::Regular, "an ordinary export is Regular"); + TEST_ASSERT_HEX_EQ(found->value, 0x1234, "an ordinary export yields its offset"); + } + } + { + TerminalSpec spec; + spec.flags = EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE; + spec.values.append(0xdeadbeef); + Vector trie = singleExportTrie("_absolute", spec); + auto found = ExportsTrie::lookUp(trie.span(), "_absolute"); + TEST_ASSERT(found, "an absolute export is found"); + if (found) { + TEST_ASSERT(found->kind == Kind::Absolute, "an absolute export is Absolute"); + TEST_ASSERT_HEX_EQ(found->value, 0xdeadbeef, "an absolute export yields the address itself"); + } + } + { + // A weak definition is still an ordinary export; the flag sits outside the + // kind mask and must not disturb it. + TerminalSpec spec; + spec.flags = EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION | EXPORT_SYMBOL_FLAGS_KIND_REGULAR; + spec.values.append(0x40); + Vector trie = singleExportTrie("_weak", spec); + auto found = ExportsTrie::lookUp(trie.span(), "_weak"); + TEST_ASSERT(found, "a weak definition is found"); + if (found) { + TEST_ASSERT(found->kind == Kind::Regular, "a weak definition is Regular"); + TEST_ASSERT_HEX_EQ(found->value, 0x40, "a weak definition yields its offset"); + } + } + { + // Per , a stub-and-resolver terminal holds two ULEB128s: + // the stub offset and then the resolver offset. The stub is the address + // the symbol resolves to; the resolver is only how a lazy binding finds it. + TerminalSpec spec; + spec.flags = EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER | EXPORT_SYMBOL_FLAGS_KIND_REGULAR; + spec.values.append(0x1000); // Stub offset. + spec.values.append(0x2000); // Resolver offset. + Vector trie = singleExportTrie("_resolved", spec); + auto found = ExportsTrie::lookUp(trie.span(), "_resolved"); + TEST_ASSERT(found, "a stub-and-resolver export is found"); + if (found) { + TEST_ASSERT(found->kind == Kind::Regular, "a stub-and-resolver export is Regular"); + TEST_ASSERT_HEX_EQ(found->value, 0x1000, "a stub-and-resolver export yields the stub offset"); + } + } + { + // The highest defined flag bit. It carries no payload of its own, so a + // terminal that sets it still decodes; nothing in dyld, ld or cctools reads + // it, which is why it must not be mistaken for an unknown bit. + TerminalSpec spec; + spec.flags = EXPORT_SYMBOL_FLAGS_STATIC_RESOLVER | EXPORT_SYMBOL_FLAGS_KIND_REGULAR; + spec.values.append(0x50); + Vector trie = singleExportTrie("_staticResolver", spec); + auto found = ExportsTrie::lookUp(trie.span(), "_staticResolver"); + TEST_ASSERT(found, "an export with the highest defined flag bit is found"); + if (found) { + TEST_ASSERT(found->kind == Kind::Regular, "a static-resolver export is Regular"); + TEST_ASSERT_HEX_EQ(found->value, 0x50, "a static-resolver export yields its offset"); + } + } + { + // A terminal may declare more room than its flags and offset need. The spare + // room is not part of the offset, and the export still resolves. + TrieBytes builder; + builder.uleb128(4); // Two bytes more than the payload below uses. + builder.uleb128(0); // Flags: an ordinary export. + builder.uleb128(0x7f); // Offset. + builder.byte(0); // Spare terminal byte. + builder.byte(0); // Spare terminal byte. + builder.byte(0); // No children. + Vector trie = builder.take(); + auto found = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(found, "a terminal with room to spare still resolves"); + if (found) + TEST_ASSERT_HEX_EQ(found->value, 0x7f, "spare terminal room is not read as the offset"); + } + { + // A look up of "" reaches the root's own terminal without walking an edge. + Vector trie = terminalOnlyTrie(regularExport(0x99)); + auto found = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(found, "a terminal at the root is found"); + if (found) + TEST_ASSERT_HEX_EQ(found->value, 0x99, "a terminal at the root yields its offset"); + } + { + // Real tries spread a name over several edges, so the walk has to cross + // more than one node to reach the terminal. + std::array edges { "_f", "oo", "bar" }; + Vector trie = chainedExportTrie(std::span(edges), regularExport(0x77)); + auto found = ExportsTrie::lookUp(trie.span(), "_foobar"); + TEST_ASSERT(found, "a name spread over several edges is found"); + if (found) + TEST_ASSERT_HEX_EQ(found->value, 0x77, "a multi-edge name yields its offset"); + + auto prefix = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!prefix && prefix.error() == Failure::Absent, + "a prefix of a multi-edge name is absent"); + auto extension = ExportsTrie::lookUp(trie.span(), "_foobarbaz"); + TEST_ASSERT(!extension && extension.error() == Failure::Absent, + "an extension of a multi-edge name is absent"); + } + { + // "_foo" and "_foobar" both exported: the shorter one lives on a node that + // also has children, so a terminal must not end the walk when name is left. + Vector trie = prefixAndChildTrie("_foo", regularExport(0x10), "bar", regularExport(0x20)); + auto shorter = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(shorter, "the shorter of two nested names is found"); + if (shorter) + TEST_ASSERT_HEX_EQ(shorter->value, 0x10, "the shorter name yields its own offset"); + auto longer = ExportsTrie::lookUp(trie.span(), "_foobar"); + TEST_ASSERT(longer, "the longer of two nested names is found"); + if (longer) + TEST_ASSERT_HEX_EQ(longer->value, 0x20, "the longer name yields its own offset"); + TEST_ASSERT(!ExportsTrie::lookUp(trie.span(), "_foobaz"), "a name that diverges is not found"); + } + { + // A node's children count is a byte rather than a ULEB128. Decoded as one, a + // count of 255 carries a continuation bit, so it would swallow the first + // character of the edge behind it: the count comes out far too large and that + // edge is read from the wrong byte. 255 edges is the widest a node can be, and + // reaching the last of them needs the walk to scan past every edge ahead of it. + Vector trie = fanOutTrie(maximumEdgeCount); + for (unsigned index : { 0u, maximumEdgeCount / 2, maximumEdgeCount - 1 }) { + auto found = ExportsTrie::lookUp(trie.span(), fanOutEdgeName(index)); + TEST_ASSERT(found, "an export is found among 255 edges"); + if (found) { + TEST_ASSERT_HEX_EQ(found->value, fanOutExportOffset(index), + "each of 255 edges leads to its own export"); + } + } + + // Nothing matches, so the walk has to scan all 255 edges and end. + auto absent = ExportsTrie::lookUp(trie.span(), "zz"); + TEST_ASSERT(!absent && absent.error() == Failure::Absent, + "a name matching none of 255 edges is Absent"); + } +} + +static void testClassifiedFailures() +{ + { + TerminalSpec spec; + spec.flags = EXPORT_SYMBOL_FLAGS_REEXPORT | EXPORT_SYMBOL_FLAGS_KIND_REGULAR; + spec.values.append(1); // Library ordinal. + spec.trailingString = "_other"; + spec.hasTrailingString = true; + Vector trie = singleExportTrie("_reexported", spec); + auto result = ExportsTrie::lookUp(trie.span(), "_reexported"); + TEST_ASSERT(!result && result.error() == Failure::ReExport, + "a re-exported name reports ReExport rather than being absent"); + } + { + TerminalSpec spec; + spec.flags = EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL; + spec.values.append(0x30); + Vector trie = singleExportTrie("_threadLocal", spec); + auto result = ExportsTrie::lookUp(trie.span(), "_threadLocal"); + TEST_ASSERT(!result && result.error() == Failure::UnsupportedKind, + "a thread-local reports UnsupportedKind: its address differs per thread"); + } + { + // The one value the kind mask can hold that Mach-O does not define. A kind + // this code does not know cannot be read as an address. + TerminalSpec spec; + spec.flags = 0x03; + spec.values.append(0x30); + Vector trie = singleExportTrie("_unknownKind", spec); + auto result = ExportsTrie::lookUp(trie.span(), "_unknownKind"); + TEST_ASSERT(!result && result.error() == Failure::UnsupportedKind, + "an unrecognized kind reports UnsupportedKind"); + } + { + Vector trie = singleExportTrie("_foo", regularExport(0x1234)); + auto missing = ExportsTrie::lookUp(trie.span(), "_bar"); + TEST_ASSERT(!missing && missing.error() == Failure::Absent, + "a name with no matching edge is Absent"); + auto shortName = ExportsTrie::lookUp(trie.span(), "_fo"); + TEST_ASSERT(!shortName && shortName.error() == Failure::Absent, + "a name shorter than the edge is Absent"); + auto emptyName = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!emptyName && emptyName.error() == Failure::Absent, + "the empty name is Absent when the root exports nothing"); + } + { + // A node with neither a terminal nor children ends the walk without an answer. + TrieBytes builder; + builder.uleb128(0); + builder.byte(0); + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Absent, "a childless root is Absent"); + } +} + +static void testMalformedTries() +{ + { + Vector empty; + auto result = ExportsTrie::lookUp(empty.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, "an empty trie is malformed"); + } + { + Vector trie { 0x80 }; // A terminal length that never ends. + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a truncated terminal length is malformed"); + } + { + // A terminal claiming more bytes than the trie has left. Rejecting this is + // what keeps the children position inside the buffer. + Vector trie { 0x7f }; + auto result = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a terminal longer than the trie is malformed"); + } + { + // A terminal length so large that adding it to the position would wrap. + TrieBytes builder; + builder.uleb128(std::numeric_limits::max()); + builder.byte(0); + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a terminal length that would wrap the position is malformed"); + } + { + Vector trie { 0x01, 0x80 }; // Terminal of one byte, holding half a ULEB128. + auto result = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!result && result.error() == Failure::Malformed, "truncated flags are malformed"); + } + { + Vector trie { 0x01, 0x00 }; // Flags say an ordinary export, but no offset follows. + auto result = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "an export with no offset is malformed"); + } + { + // Flags promise a stub offset that the trie does not hold. + TrieBytes builder; + TrieBytes payload; + payload.uleb128(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER | EXPORT_SYMBOL_FLAGS_KIND_REGULAR); + Vector payloadBytes = payload.take(); + builder.uleb128(payloadBytes.size()); + builder.bytes(payloadBytes.span()); + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a stub-and-resolver export with no offsets is malformed"); + } + { + // Only six flag bits are defined. An unknown one may carry a ULEB128 ahead of + // the address, so the offset that follows it cannot be trusted to be one. + TerminalSpec spec; + spec.flags = 0x40; + spec.values.append(0x42); + Vector trie = singleExportTrie("_unknownFlag", spec); + auto result = ExportsTrie::lookUp(trie.span(), "_unknownFlag"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a terminal with an unknown flag bit is malformed"); + } + { + // A terminal that declares only its flags holds no offset. Reading one anyway + // would take the children count that follows it as the symbol's address. + TrieBytes builder; + builder.uleb128(1); // Terminal length: room for the flags alone. + builder.uleb128(0); // Flags: an ordinary export, which needs an offset... + builder.byte(2); // ...but this is the children count, not one. + builder.cString("a"); + builder.uleb128(0); + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a terminal that ends before its offset is malformed"); + } + { + // The offset's encoding runs off the end of the terminal, so completing it + // would take a byte belonging to the children. + TrieBytes builder; + builder.uleb128(2); // Terminal length: the flags and one offset byte. + builder.uleb128(0); // Flags. + builder.byte(0x80); // First byte of a two-byte offset... + builder.byte(0x01); // ...whose second byte lies outside the terminal. + builder.byte(0); // No children. + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), ""); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "an offset whose encoding leaves the terminal is malformed"); + } + { + // The children count sits past the end of the trie. + Vector trie { 0x00 }; + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a missing children count is malformed"); + } + { + TrieBytes builder; + builder.uleb128(0); + builder.byte(1); + builder.string("_foo"); // No terminator. + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "an unterminated edge is malformed"); + } + { + // An empty edge would match anything and consume none of the name, which is + // what would let a walk run forever. + TrieBytes builder; + builder.uleb128(0); + builder.byte(1); + builder.cString(""); + builder.uleb128(0); + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, "an empty edge is malformed"); + } + { + TrieBytes builder; + builder.uleb128(0); + builder.byte(1); + builder.cString("_foo"); + builder.byte(0x80); // A child offset that never ends. + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a truncated child offset is malformed"); + } + { + // An edge leading outside the trie. + TrieBytes builder; + builder.uleb128(0); + builder.byte(1); + builder.cString("_foo"); + builder.uleb128(1000); + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result && result.error() == Failure::Malformed, + "a child offset past the end of the trie is malformed"); + } + { + // An edge leading into the middle of the node it came from. Whatever that + // decodes to, it must be an answer rather than a crash or a hang. + TrieBytes builder; + builder.uleb128(0); + builder.byte(1); + builder.cString("_foo"); + builder.uleb128(2); + Vector trie = builder.take(); + auto result = ExportsTrie::lookUp(trie.span(), "_foo"); + TEST_ASSERT(!result, "a child offset into the middle of a node yields no export"); + } + { + // A trie truncated part way through a node it claims to hold. The last byte + // is the terminal node's children count, which a look up that ends at that + // terminal never reads, so removing only that byte still resolves; every + // shorter prefix cuts into the terminal itself and must not. + Vector trie = singleExportTrie("_foo", regularExport(0x1234)); + for (size_t length = 1; length + 1 < trie.size(); ++length) { + auto result = ExportsTrie::lookUp(trie.span().first(length), "_foo"); + TEST_ASSERT(!result, "a truncated trie yields no export"); + } + } +} + +static void testWalkIsBounded() +{ + // A node whose only edge leads back to itself. The walk may only follow an edge + // by consuming at least one character of the name, so it has to end even though + // the trie describes a cycle. Without that property this test would not return. + TrieBytes builder; + builder.uleb128(0); + builder.byte(1); + builder.cString("a"); + builder.uleb128(0); // Back to the root. + Vector trie = builder.take(); + + std::string name(20000, 'a'); + auto result = ExportsTrie::lookUp(trie.span(), name); + TEST_ASSERT(!result, "a cyclic trie yields no export"); + + // The same cycle reached with a name it cannot consume. + auto other = ExportsTrie::lookUp(trie.span(), "b"); + TEST_ASSERT(!other && other.error() == Failure::Absent, "a cycle whose edge does not match is Absent"); +} + +void testExportsTrie() +{ + if (!beginSuite("ExportsTrie")) + return; + + testFoundExports(); + testClassifiedFailures(); + testMalformedTries(); + testWalkIsBounded(); +} + +// A small deterministic generator, so that a failure can be reproduced from the +// seed the run reports. +class Random { +public: + explicit Random(uint64_t seed) + : m_state(seed ? seed : 0x9e3779b97f4a7c15ull) + { + } + + uint64_t next() + { + m_state ^= m_state >> 12; + m_state ^= m_state << 25; + m_state ^= m_state >> 27; + return m_state * 0x2545f4914f6cdd1dull; + } + + uint32_t below(uint32_t bound) { return bound ? static_cast(next() % bound) : 0; } + +private: + uint64_t m_state; +}; + +static Atomic fuzzIteration; +static Atomic fuzzFinished; + +static void* fuzzWatchdog(void*) +{ + uint64_t lastSeen = 0; + unsigned stalledPolls = 0; + static constexpr unsigned pollIntervalUsec = 250 * 1000; + static constexpr unsigned stallLimitPolls = 40; // Ten seconds. + + while (!fuzzFinished.load()) { + usleep(pollIntervalUsec); + uint64_t current = fuzzIteration.load(); + if (current != lastSeen) { + lastSeen = current; + stalledPolls = 0; + continue; + } + if (++stalledPolls < stallLimitPolls) + continue; + // The decoder promises to bound its work on any input. A stall means it + // does not, so crash here rather than let the run hang: a report with a + // stack in the decoder says far more than a timeout does. + dataLogLn("FAIL: exports trie look up did not finish on fuzz iteration ", lastSeen); + CRASH(); + } + return nullptr; +} + +void fuzzExportsTrie(uint64_t seed, unsigned iterations) +{ + if (!beginSuite("ExportsTrie fuzz")) + return; + dataLogLn(" seed ", RawHex(seed), ", ", iterations, " iterations"); + + Random random(seed); + fuzzIteration.store(0); + fuzzFinished.store(false); + + pthread_t watchdog { }; + bool watching = !pthread_create(&watchdog, nullptr, fuzzWatchdog, nullptr); + TEST_ASSERT(watching, "the fuzz watchdog started"); + + Vector valid = singleExportTrie("_foo", regularExport(0x1234)); + std::array names { "_foo", "_foobar", "_f", "", "_bar", "_fop" }; + + for (unsigned iteration = 0; iteration < iterations; ++iteration) { + fuzzIteration.store(iteration + 1); + + Vector trie; + if (random.below(4)) { + // Mostly near-valid tries: those reach further into the decoder than + // noise does, because their early fields still make sense. + trie = valid; + unsigned mutations = 1 + random.below(6); + for (unsigned mutation = 0; mutation < mutations; ++mutation) + trie[random.below(static_cast(trie.size()))] = static_cast(random.next()); + } else { + unsigned length = random.below(64); + for (unsigned index = 0; index < length; ++index) + trie.append(static_cast(random.next())); + } + + std::string generatedName; + std::string_view name; + if (random.below(4)) + name = names[random.below(names.size())]; + else { + unsigned length = random.below(12); + for (unsigned index = 0; index < length; ++index) + generatedName += static_cast('_' + random.below(48)); + name = generatedName; + } + + auto result = ExportsTrie::lookUp(trie.span(), name); + // Noise is allowed to decode as an export. What is not allowed is an + // export of a kind the caller cannot act on. + if (result) { + TEST_ASSERT(result->kind == Kind::Regular || result->kind == Kind::Absolute, + "a decoded export has a kind the caller understands"); + } + } + + fuzzFinished.store(true); + if (watching) + pthread_join(watchdog, nullptr); +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.h b/Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.h new file mode 100644 index 000000000000..29e8bc6b4f8f --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseExportsTrieTest.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include + +namespace JSCToolsTest { + +void testExportsTrie(); + +// Feeds mutated and random tries to the decoder. A trie out of a corpse is +// untrusted, and the decoder promises to bound its work on any input, which only +// a run over inputs nobody wrote can really check. +void fuzzExportsTrie(uint64_t seed, unsigned iterations); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseProcessTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseProcessTest.cpp new file mode 100644 index 000000000000..445ceb9d5b20 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseProcessTest.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseProcessTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include +#include +#include + +namespace JSCToolsTest { + +using JSC::Corpse::Process; + +// A pid that is certainly not in use: a child that has been reaped. Returns 0 if +// no child could be made. +static pid_t reapedChildPid() +{ + pid_t child = fork(); + if (!child) + _exit(0); + if (child < 0) + return 0; + int status = 0; + if (waitpid(child, &status, 0) != child) + return 0; + return child; +} + +#if CPU(ARM64) +// Launches /bin/sh as x86_64, which on Apple silicon means under translation. +// Returns 0 if this machine cannot run x86_64 code. +static pid_t spawnTranslatedChild() +{ + posix_spawnattr_t attributes; + if (posix_spawnattr_init(&attributes)) + return 0; + + cpu_type_t preference = CPU_TYPE_X86_64; + size_t counted = 0; + posix_spawnattr_setbinpref_np(&attributes, 1, &preference, &counted); + + char* const arguments[] = { + const_cast("/bin/sh"), + const_cast("-c"), + const_cast("sleep 30"), + nullptr + }; + pid_t child = 0; + int error = posix_spawn(&child, "/bin/sh", nullptr, &attributes, arguments, nullptr); + posix_spawnattr_destroy(&attributes); + + if (error || counted != 1) + return 0; + return child; +} +#endif // CPU(ARM64) + +void testProcess() +{ + if (!beginSuite("Process")) + return; + + { + RefPtr process = Process::create(getpid()); + TEST_ASSERT(!process->isAttached(), "a new Process is not attached"); + TEST_ASSERT_EQ(process->pid(), getpid(), "a Process keeps the pid it was given"); + + TEST_ASSERT(process->attach(), "attaching to this process succeeds"); + TEST_ASSERT(process->isAttached(), "attaching leaves the Process attached"); + TEST_ASSERT(process->holdsLiveTask(), "the task port names this very process"); + TEST_ASSERT(process->attach(), "attaching an already attached Process succeeds"); + + process->detach(); + TEST_ASSERT(!process->isAttached(), "detaching releases the task port"); + TEST_ASSERT(!process->holdsLiveTask(), "a detached Process holds no task"); + + TEST_ASSERT(process->attach(), "a detached Process can attach again"); + process->detach(); + process->detach(); + TEST_ASSERT(!process->isAttached(), "detaching twice is harmless"); + } + { + // Attaching takes a send right to the target's task port, and every path out of + // an attach has to give it back. Attaching to this very process yields the name + // this task already holds for itself, so the kernel adds a reference to that + // name rather than handing out a new one: a right that is never given back + // shows up in the reference count and not in the size of the name space. + unsigned namesBefore = machPortNameCount(); + mach_port_t port = MACH_PORT_NULL; + unsigned refsAttached = 0; + { + RefPtr process = Process::create(getpid()); + TEST_ASSERT(process->attach(), "attaching to this process succeeds"); + if (process->isAttached()) { + port = process->taskPort(); + refsAttached = machPortSendRightCount(port); + TEST_ASSERT(refsAttached, "an attached Process holds a send right to the task port"); + + process->attach(); + TEST_ASSERT_EQ(machPortSendRightCount(port), refsAttached, + "attaching an attached Process takes no further send right"); + + process->detach(); + TEST_ASSERT_EQ(machPortSendRightCount(port), refsAttached - 1, + "detaching gives the send right back"); + process->detach(); + TEST_ASSERT_EQ(machPortSendRightCount(port), refsAttached - 1, + "detaching twice gives back only what one attach took"); + + process->attach(); // Left attached, so the destructor has to release it. + } + } + if (refsAttached) { + TEST_ASSERT_EQ(machPortSendRightCount(port), refsAttached - 1, + "destroying an attached Process gives its send right back"); + TEST_ASSERT_EQ(machPortNameCount(), namesBefore, + "attaching and detaching leaves no port name behind"); + } + } + { + pid_t gone = reapedChildPid(); + if (!gone) + TEST_ASSERT(gone, "a child could be forked and reaped"); + else { + dataLogLn(" (the next line is the failure this test asks for)"); + unsigned namesBefore = machPortNameCount(); + RefPtr process = Process::create(gone); + TEST_ASSERT(!process->attach(), "attaching to a process that has exited fails"); + TEST_ASSERT(!process->isAttached(), "a failed attach leaves the Process unattached"); + TEST_ASSERT_EQ(machPortNameCount(), namesBefore, "a failed attach leaves no port name behind"); + } + } + { + RefPtr process = Process::create(getpid()); + TEST_ASSERT(!process->isTranslated(), "this process does not run under translation"); + // Answering this needs no task port, only the pid. + TEST_ASSERT(!process->isAttached(), "asking about translation does not attach"); + } + { + RefPtr initProcess = Process::create(1); + TEST_ASSERT(!initProcess->isTranslated(), "launchd does not run under translation"); + } + { + pid_t gone = reapedChildPid(); + if (gone) { + RefPtr process = Process::create(gone); + TEST_ASSERT(!process->isTranslated(), "a process that has exited is not translated"); + } + } +#if CPU(ARM64) + { + pid_t translated = spawnTranslatedChild(); + if (!translated) + skipSuite("Process translation", "this machine cannot run x86_64 code"); + else { + RefPtr process = Process::create(translated); + TEST_ASSERT(process->isTranslated(), "a process running x86_64 code is translated"); + kill(translated, SIGKILL); + int status = 0; + waitpid(translated, &status, 0); + } + } +#endif // CPU(ARM64) +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseProcessTest.h b/Source/JavaScriptCore/corpse/tests/CorpseProcessTest.h new file mode 100644 index 000000000000..1245c56e1e07 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseProcessTest.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSCToolsTest { + +void testProcess(); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseRegionTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseRegionTest.cpp new file mode 100644 index 000000000000..813b24717cd7 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseRegionTest.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseRegionTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JSCToolsTest { + +using JSC::Corpse::Address; +using JSC::Corpse::Region; + +void testRegion() +{ + if (!beginSuite("Region")) + return; + + size_t pageSize = static_cast(getpagesize()); + static constexpr size_t mappedPages = 5; + static constexpr size_t writtenPages = 2; + static constexpr size_t readPages = 1; // Exclusive of writtenPages. + + // The test scratch area is seven pages, with the first and last given back (holes), so + // that the five in the middle are a region of known size with a hole on either side. + // In this test, we'll check on those hole pages being holes. Hence, we need for them to + // stay unmapped. + // + // Unfortunately, bmalloc or other heaps, when expanding, may consume the lower unmapped + // page, and put it to use. This breaks our reliance on it being unmapped. So, we'll + // precede the test scratch area with a runway of 10 unmapped pages. This gives any heap + // some room to grow into without picking off our hole pages. + static constexpr size_t runwayPaddingPages = 10; + static constexpr size_t lowerSentinelPage = runwayPaddingPages; + static constexpr size_t holeBelowRegion = lowerSentinelPage + 1; + static constexpr size_t holeAboveRegion = holeBelowRegion + mappedPages + 1; + static constexpr size_t upperSentinelPage = holeAboveRegion + 1; + static constexpr size_t reservationPages = upperSentinelPage + 1; + + size_t reservationSize = reservationPages * pageSize; + void* reservation = mmap(nullptr, reservationSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (reservation == MAP_FAILED) { + TEST_ASSERT(false, "the required test VA should be mappable"); + return; + } + auto addressAt = [&](size_t pageIndex) { + return reinterpret_cast(reservation) + pageIndex * pageSize; + }; + auto unmapPages = [&](size_t pageIndex, size_t pageCount) { + munmap(reinterpret_cast(addressAt(pageIndex)), pageCount * pageSize); + }; + unmapPages(0, runwayPaddingPages); + unmapPages(holeBelowRegion, 1); + unmapPages(holeAboveRegion, 1); + + uintptr_t base = addressAt(holeBelowRegion + 1); + for (size_t page = 0; page < writtenPages; ++page) + *reinterpret_cast(base + page * pageSize) = 1; // Touch with write. + for (size_t page = writtenPages; page < writtenPages + readPages; ++page) + (void)*reinterpret_cast(base + page * pageSize); // Touch with read. + + // Gives back everything this test still holds: the region and the two sentinels. + // The runwayPaddingPages are already unmapped. + auto unmapPagesStillHeld = [&]() { + unmapPages(lowerSentinelPage, 1); + unmapPages(holeBelowRegion + 1, mappedPages); + unmapPages(upperSentinelPage, 1); + }; + + SelfSnapshot self; + if (!self.isValid()) { + unmapPagesStillHeld(); + return; + } + mach_port_t corpsePort = self.snapshot().corpsePort(); + + { + auto region = Region::findContaining(corpsePort, Address(static_cast(base))); + TEST_ASSERT(region, "the region holding a known mapping is found"); + if (region) { + TEST_ASSERT_HEX_EQ(region->base().toMachVMAddress(), base, "the region starts where the mapping does"); + TEST_ASSERT_EQ(region->size(), mappedPages * pageSize, "the region is as large as the mapping"); + TEST_ASSERT_HEX_EQ(region->end().toMachVMAddress(), base + mappedPages * pageSize, + "the region ends where the mapping does"); + TEST_ASSERT_EQ(region->pageCount(), + static_cast(mappedPages * pageSize / vm_kernel_page_size), + "the region holds as many pages as were mapped"); + TEST_ASSERT(region->contains(region->base()), "a region contains its first byte"); + TEST_ASSERT(region->contains(region->end() - 1), "a region contains its last byte"); + TEST_ASSERT(!region->contains(region->end()), "a region does not contain the byte past its end"); + TEST_ASSERT(!region->contains(region->base() - 1), "a region does not contain the byte before it"); + + uint64_t accessedKernelPages = + static_cast((writtenPages + readPages) * pageSize / vm_kernel_page_size); + TEST_ASSERT_EQ(region->residentPageCount(), accessedKernelPages, + "the pages that were accessed are the resident ones"); + + // Dirty does not mean written: an anonymous page has no pager to be re-read + // from, so it counts as dirty from the moment a fault creates it, whether + // that fault was a write or a read. The page that was only read is dirty in + // most runs but not all, so the written pages are what can be counted on. + uint64_t writtenKernelPages = static_cast(writtenPages * pageSize / vm_kernel_page_size); + TEST_ASSERT(region->dirtyPageCount() >= writtenKernelPages + && region->dirtyPageCount() <= accessedKernelPages, + "every page that was written is dirty and no page that was never accessed is"); + } + } + { + // An address in the middle of the mapping still finds the whole region. + auto region = Region::findContaining(corpsePort, + Address(static_cast(base + pageSize + 16))); + TEST_ASSERT(region, "an address inside the mapping finds the region"); + if (region) + TEST_ASSERT_HEX_EQ(region->base().toMachVMAddress(), base, "any address in a region finds its base"); + } + { + // The kernel reports the region at or above the address it is asked about, + // so a hole must be reported as a hole rather than as the region above it. + auto region = Region::findContaining(corpsePort, + Address(static_cast(addressAt(holeBelowRegion)))); + TEST_ASSERT(!region, "an address in an unmapped hole finds no region"); + } + { + // The same question asked from the other side. An address in this hole sits past + // the end of the region below it, which must not be reported as containing it. + auto region = Region::findContaining(corpsePort, + Address(static_cast(addressAt(holeAboveRegion)))); + TEST_ASSERT(!region, "an address in the hole above a region finds no region"); + } + { + // The shared cache is mapped as a submap, which the search has to descend + // into before it can describe what is actually there. A function's address + // arrives signed on arm64e, and is an address only once stripped. + Address inSharedCache = Address(reinterpret_cast(&memcpy)).stripped(); + auto region = Region::findContaining(corpsePort, inSharedCache); + TEST_ASSERT(region, "an address in the shared cache finds a region"); + if (region) + TEST_ASSERT(region->size(), "a shared cache region has a size"); + } + + unmapPagesStillHeld(); +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseRegionTest.h b/Source/JavaScriptCore/corpse/tests/CorpseRegionTest.h new file mode 100644 index 000000000000..b2c14df1c4db --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseRegionTest.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSCToolsTest { + +// Takes a corpse of the running test itself, so that everything the corpse +// reports can be checked against what this process already knows. + +void testRegion(); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.cpp new file mode 100644 index 000000000000..92a922f913b3 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseSnapshotTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include +#include + +namespace JSCToolsTest { + +using JSC::Corpse::Process; +using JSC::Corpse::Snapshot; + +void testSnapshot() +{ + if (!beginSuite("Snapshot")) + return; + + RefPtr process = Process::create(getpid()); + if (!process->attach()) { + TEST_ASSERT(false, "attaching to this process succeeds"); + return; + } + + unsigned firstId = 0; + { + Snapshot snapshot(process); + TEST_ASSERT(snapshot.isValid(), "a snapshot of this process is valid"); + TEST_ASSERT(MACH_PORT_VALID(snapshot.corpsePort()), "a valid snapshot holds a corpse port"); + TEST_ASSERT(snapshot.process() == process.get(), "a snapshot keeps the process it came from"); + firstId = snapshot.id(); + TEST_ASSERT(firstId, "a snapshot has an identifier"); + + Snapshot second(process); + TEST_ASSERT(second.isValid(), "a second snapshot of the same process is valid"); + TEST_ASSERT(second.id() > firstId, "identifiers increase"); + TEST_ASSERT(second.corpsePort() != snapshot.corpsePort(), + "two snapshots hold two different corpses"); + } + { + // The two above are gone; their identifiers must not come back. + Snapshot later(process); + TEST_ASSERT(later.id() > firstId + 1, "identifiers are not reused after a snapshot is destroyed"); + } + { + RefPtr unattached = Process::create(getpid()); + Snapshot snapshot(unattached); + TEST_ASSERT(!snapshot.isValid(), "a snapshot of an unattached process is invalid"); + TEST_ASSERT(snapshot.threads().isEmpty(), "an invalid snapshot reports no threads"); + TEST_ASSERT(!snapshot.symbol("g_config"), "an invalid snapshot resolves no symbol"); + } + { + RefPtr none; + Snapshot snapshot(none); + TEST_ASSERT(!snapshot.isValid(), "a snapshot with no process is invalid"); + } + { + Snapshot snapshot(process); + TEST_ASSERT(!snapshot.symbol(nullptr), "an unnamed symbol resolves to nothing"); + TEST_ASSERT(!snapshot.symbol(""), "an empty symbol name resolves to nothing"); + } + + { + // A corpse and the threads read out of it are Mach ports. Taking many + // snapshots must not leave any of them behind. + static constexpr unsigned rounds = 100; + unsigned before = machPortNameCount(); + for (unsigned round = 0; round < rounds; ++round) { + Snapshot snapshot(process); + if (!snapshot.isValid()) + continue; + snapshot.threads(); + } + unsigned after = machPortNameCount(); + // A handful of names may come and go for reasons of their own; a leak of + // one port per round would be a hundred. + static constexpr unsigned allowedDrift = 8; + TEST_ASSERT(after <= before + allowedDrift, "taking and dropping snapshots leaks no Mach port"); + if (after > before + allowedDrift) + dataLogLn(" port names before ", before, ", after ", after, ", over ", rounds, " snapshots"); + } +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.h b/Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.h new file mode 100644 index 000000000000..4a0b7759f219 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseSnapshotTest.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSCToolsTest { + +// Takes a corpse of the running test itself, so that everything the corpse +// reports can be checked against what this process already knows. + +void testSnapshot(); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.cpp new file mode 100644 index 000000000000..8bb461be59e2 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseSymbolTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Exists in this binary but is not exported, so an exports trie cannot see it. +// A look up must report that rather than find it some other way. +extern "C" __attribute__((visibility("hidden"))) int jscToolsTestHiddenGlobal; +int jscToolsTestHiddenGlobal = 42; + +namespace JSCToolsTest { + +using JSC::Corpse::Address; +using JSC::Corpse::Snapshot; +using JSC::Corpse::Symbol; + +// A symbol resolved out of a corpse of this very process must land on the same +// address this process would use, because the corpse is a copy of this address +// space. That makes every look up below checkable against ground truth. +void testSymbol() +{ + if (!beginSuite("Symbol")) + return; + + SelfSnapshot self; + if (!self.isValid()) + return; + Snapshot& snapshot = self.snapshot(); + + { + // A data symbol exported by the JavaScriptCore framework: an image that is + // not in the shared cache, so this checks the slide from __TEXT's link-time + // address to where the image actually landed. + auto expected = reinterpret_cast(WebConfig::g_config); + Address found = snapshot.symbol("g_config"); + TEST_ASSERT(found, "a symbol exported by JavaScriptCore is found"); + TEST_ASSERT_HEX_EQ(found.toMachVMAddress(), expected, + "g_config resolves to the address this process uses for it"); + } + { + // A function in the shared cache, where __LINKEDIT is shared between images + // and the trie is reached by a different route. + void* expected = dlsym(RTLD_DEFAULT, "malloc"); + TEST_ASSERT(expected, "malloc can be looked up locally"); + Address found = snapshot.symbol("malloc"); + TEST_ASSERT(found, "a symbol exported by a shared cache image is found"); + // A function pointer arrives signed on arm64e; only the address it names is + // being compared here. + TEST_ASSERT_HEX_EQ(found.stripped().toMachVMAddress(), + Address(expected).stripped().toMachVMAddress(), + "malloc resolves to the address this process uses for it"); + } + { + // A data symbol in the shared cache. + void* expected = dlsym(RTLD_DEFAULT, "environ"); + if (!expected) + skipSuite("Symbol environ", "this system does not export environ"); + else { + Address found = snapshot.symbol("environ"); + TEST_ASSERT(found, "a data symbol in the shared cache is found"); + TEST_ASSERT_HEX_EQ(found.stripped().toMachVMAddress(), + Address(expected).stripped().toMachVMAddress(), + "environ resolves to the address this process uses for it"); + } + } + { + TEST_ASSERT(!snapshot.symbol("jscToolsTestNoSuchSymbolAnywhere"), + "a name that is not exported anywhere is not found"); + TEST_ASSERT(!snapshot.symbol(nullptr), "no name resolves to nothing"); + TEST_ASSERT(!snapshot.symbol(""), "an empty name resolves to nothing"); + } + { + // Only exported symbols appear in a trie. This one is in the binary, and + // still must not be found: saying so is the honest answer. + TEST_ASSERT(jscToolsTestHiddenGlobal == 42, "the hidden global is in this binary"); + TEST_ASSERT(!snapshot.symbol("jscToolsTestHiddenGlobal"), + "a symbol hidden from the linker is not found"); + } + { + // A look up prepends the underscore that a Mach-O symbol name carries, so a + // name that already has one is asking for a different symbol. + TEST_ASSERT(!snapshot.symbol("_malloc"), + "a name given with its underscore already attached is not found"); + } + { + // Resolving is expensive, so a snapshot keeps what it has resolved. + Address first = snapshot.symbol("g_config"); + Address second = snapshot.symbol("g_config"); + TEST_ASSERT(first == second, "resolving the same name twice gives the same address"); + } + { + Symbol symbol(snapshot, "g_config"); + TEST_ASSERT(symbol.name() == "g_config", "a Symbol keeps the name it was asked for"); + TEST_ASSERT(symbol.isValid(), "a Symbol that resolved is valid"); + TEST_ASSERT(symbol.address() == snapshot.symbol("g_config"), + "a Symbol resolves to what the snapshot reports"); + + Symbol missing(snapshot, "jscToolsTestNoSuchSymbolAnywhere"); + TEST_ASSERT(!missing.isValid(), "a Symbol that did not resolve is not valid"); + TEST_ASSERT(!missing.address(), "a Symbol that did not resolve has no address"); + TEST_ASSERT(missing.name() == "jscToolsTestNoSuchSymbolAnywhere", + "a Symbol that did not resolve still knows its name"); + + Symbol unnamed(snapshot, nullptr); + TEST_ASSERT(unnamed.name().empty(), "a Symbol with no name has an empty name"); + TEST_ASSERT(!unnamed.isValid(), "a Symbol with no name is not valid"); + } + { + // A name that is nowhere walks every image in the corpse, which is the most + // work a look up can be asked to do. It has to stay bounded. + static constexpr double budgetSeconds = 60; + MonotonicTime start = MonotonicTime::now(); + TEST_ASSERT(!snapshot.symbol("jscToolsTestAnotherNameThatIsNowhere"), + "an absent name is reported absent"); + double elapsed = (MonotonicTime::now() - start).seconds(); + TEST_ASSERT(elapsed < budgetSeconds, "a look up that finds nothing still finishes"); + if (elapsed >= budgetSeconds) + dataLogLn(" the search took ", elapsed, " seconds"); + } +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.h b/Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.h new file mode 100644 index 000000000000..e66d0a11c6a6 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseSymbolTest.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSCToolsTest { + +void testSymbol(); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseThreadTest.cpp b/Source/JavaScriptCore/corpse/tests/CorpseThreadTest.cpp new file mode 100644 index 000000000000..5766b8a4a58f --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseThreadTest.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CorpseThreadTest.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include +#include +#include + +namespace JSCToolsTest { + +using JSC::Corpse::Thread; + +void testThreads() +{ + if (!beginSuite("Thread")) + return; + + static constexpr const char* alphaName = "jsctools alpha"; + static constexpr const char* betaName = "jsctools beta"; + // Longer than a pthread name can hold, so that truncation is exercised. + static constexpr const char* longName = + "jsctools a thread whose name is far too long to fit in the space a pthread name has"; + + ParkedThreads parked; + TEST_ASSERT(parked.spawn(alphaName), "a named thread starts"); + TEST_ASSERT(parked.spawn(betaName), "a second named thread starts"); + TEST_ASSERT(parked.spawn(longName), "a thread with an overlong name starts"); + if (!parked.waitUntilAllParked()) { + TEST_ASSERT(false, "the spawned threads parked themselves"); + return; + } + + SelfSnapshot self; + if (!self.isValid()) + return; + + const Vector& threads = self.snapshot().threads(); + TEST_ASSERT(threads.size() >= 1 + parked.count(), + "the corpse holds at least this process's own threads"); + + bool foundAlpha = false; + bool foundBeta = false; + bool foundTruncated = false; + std::string expectedTruncated(std::string_view(longName).substr(0, ParkedThreads::maximumNameLength)); + + for (const Thread& thread : threads) { + if (thread.name() == alphaName) + foundAlpha = true; + else if (thread.name() == betaName) + foundBeta = true; + else if (thread.name() == expectedTruncated) + foundTruncated = true; + + TEST_ASSERT(thread.id(), "every thread has an identifier"); + TEST_ASSERT(thread.name().length() <= ParkedThreads::maximumNameLength, + "no thread name is longer than a pthread name can be"); + + // The stack is defined as the region the stack pointer points into, so if + // both were read they have to agree. + if (thread.stackPointer()) { + TEST_ASSERT(thread.hasStack(), "a thread with a stack pointer has a stack region"); + if (thread.hasStack()) { + TEST_ASSERT(thread.stackRegion().contains(thread.stackPointer()), + "a thread's stack pointer lies inside its stack region"); + TEST_ASSERT(thread.stackRegion().pageCount() >= thread.stackRegion().residentPageCount(), + "a stack has at least as many pages as it has resident"); + } + } + + TEST_ASSERT(!std::string_view(thread.runStateDescription()).empty(), + "a thread's run state has a name"); + } + + TEST_ASSERT(foundAlpha, "a named thread appears in the corpse under its name"); + TEST_ASSERT(foundBeta, "a second named thread appears under its name"); + TEST_ASSERT(foundTruncated, "an overlong thread name appears cut to what a pthread name holds"); + + // Reading the threads is the expensive part, so it happens once. + const Vector& again = self.snapshot().threads(); + TEST_ASSERT(&again == &threads, "the threads of a snapshot are read once and kept"); + + parked.stopAndJoin(); +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/CorpseThreadTest.h b/Source/JavaScriptCore/corpse/tests/CorpseThreadTest.h new file mode 100644 index 000000000000..8a897ad7551c --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/CorpseThreadTest.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +namespace JSCToolsTest { + +// Takes a corpse of the running test itself, so that everything the corpse +// reports can be checked against what this process already knows. + +void testThreads(); + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.cpp b/Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.cpp new file mode 100644 index 000000000000..ad71dbe0365f --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "LibJSCToolsTestUtilities.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JSCToolsTest { + +unsigned assertionsRun = 0; +unsigned assertionsFailed = 0; +unsigned suitesSkipped = 0; +const char* suiteFilter = nullptr; + +bool beginSuite(const char* name) +{ + if (suiteFilter && !std::string_view(name).contains(std::string_view(suiteFilter))) + return false; + dataLogLn("--- ", name); + return true; +} + +void skipSuite(const char* name, const char* why) +{ + ++suitesSkipped; + dataLogLn("SKIP: ", name, ": ", why); +} + +unsigned machPortNameCount() +{ + mach_port_name_array_t names = nullptr; + mach_msg_type_number_t nameCount = 0; + mach_port_type_array_t types = nullptr; + mach_msg_type_number_t typeCount = 0; + auto result = mach_port_names(mach_task_self(), &names, &nameCount, &types, &typeCount); + RELEASE_ASSERT(result == KERN_SUCCESS); + + mach_vm_deallocate(mach_task_self(), reinterpret_cast(names), nameCount * sizeof(mach_port_name_t)); + mach_vm_deallocate(mach_task_self(), reinterpret_cast(types), typeCount * sizeof(mach_port_type_t)); + return nameCount; +} + +unsigned machPortSendRightCount(mach_port_t port) +{ + mach_port_urefs_t refs = 0; + auto result = mach_port_get_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, &refs); + if (result == KERN_INVALID_NAME) { + // A name this task does not hold is an answer -- it holds no rights under it -- + // rather than a failure. Hence, has no send right. + return 0; + } + RELEASE_ASSERT(result == KERN_SUCCESS); + return refs; // Can still be 0 (which still means no send right). +} + +SelfSnapshot::SelfSnapshot() +{ + m_process = JSC::Corpse::Process::create(getpid()); + if (!m_process->attach()) { + TEST_ASSERT(false, "attaching to this process succeeds"); + return; + } + m_snapshot = WTF::makeUnique(m_process); + if (!m_snapshot->isValid()) + TEST_ASSERT(false, "a snapshot of this process is valid"); +} + +SelfSnapshot::~SelfSnapshot() = default; + +bool SelfSnapshot::isValid() const +{ + return m_snapshot && m_snapshot->isValid(); +} + +JSC::Corpse::Snapshot& SelfSnapshot::snapshot() const +{ + return *m_snapshot; +} + +RefPtr SelfSnapshot::process() const +{ + return m_process; +} + +// One control block for all parked threads, so that they can be told to stop +// together. Only one ParkedThreads is expected to be alive at a time. +static pthread_mutex_t parkMutex = PTHREAD_MUTEX_INITIALIZER; +static unsigned parkedCount = 0; +static bool parkStopping = false; + +struct ParkedThreads::Thread { + pthread_t handle { }; + std::string name; +}; + +static void* parkThread(void* argument) +{ + auto* thread = static_cast(argument); + pthread_setname_np(thread->name.c_str()); + + pthread_mutex_lock(&parkMutex); + ++parkedCount; + while (!parkStopping) { + pthread_mutex_unlock(&parkMutex); + usleep(1000); + pthread_mutex_lock(&parkMutex); + } + pthread_mutex_unlock(&parkMutex); + return nullptr; +} + +ParkedThreads::~ParkedThreads() +{ + stopAndJoin(); +} + +bool ParkedThreads::spawn(const char* name) +{ + auto* thread = new Thread; + // pthread cuts a name that does not fit, and so must this copy, so that the + // name asked for here is the name a corpse will report. + thread->name = std::string_view(name).substr(0, maximumNameLength); + if (pthread_create(&thread->handle, nullptr, parkThread, thread)) { + delete thread; + return false; + } + m_threads.append(thread); + return true; +} + +bool ParkedThreads::waitUntilAllParked() +{ + // Bounded so that a thread that never starts fails the test rather than + // hanging it. + for (unsigned attempt = 0; attempt < 5000; ++attempt) { + pthread_mutex_lock(&parkMutex); + bool ready = parkedCount >= m_threads.size(); + pthread_mutex_unlock(&parkMutex); + if (ready) { + // A thread counts itself as parked just before it settles into its + // wait, so give it that moment before anything reads its state. + usleep(50 * 1000); + return true; + } + usleep(1000); + } + return false; +} + +void ParkedThreads::stopAndJoin() +{ + if (m_threads.isEmpty()) + return; + + pthread_mutex_lock(&parkMutex); + parkStopping = true; + pthread_mutex_unlock(&parkMutex); + + for (Thread* thread : m_threads) { + pthread_join(thread->handle, nullptr); + delete thread; + } + m_threads.clear(); + + pthread_mutex_lock(&parkMutex); + parkStopping = false; + parkedCount = 0; + pthread_mutex_unlock(&parkMutex); +} + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.h b/Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.h new file mode 100644 index 000000000000..e34a9abc3c32 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/LibJSCToolsTestUtilities.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JSC { +namespace Corpse { +class Process; +class Snapshot; +} +} + +namespace JSCToolsTest { + +// Assertions report against these, and main reports the totals. +extern unsigned assertionsRun; +extern unsigned assertionsFailed; +extern unsigned suitesSkipped; + +// Only suites whose name contains this substring run. Null runs all of them. +extern const char* suiteFilter; + +bool beginSuite(const char* name); +void skipSuite(const char* name, const char* why); + +// Nothing is printed for a passing assertion: a passing run should be quiet, and +// a failing one should say only what failed. +#define TEST_ASSERT(condition, message) \ + do { \ + ++JSCToolsTest::assertionsRun; \ + if (!(condition)) { \ + ++JSCToolsTest::assertionsFailed; \ + dataLogLn("FAIL: ", message, " (", #condition, ") at ", __FILE__, ":", __LINE__); \ + } \ + } while (0) + +// For values dataLog can print. Reports both sides, which is what makes a +// failure diagnosable without a debugger. +#define TEST_ASSERT_EQ(actual, expected, message) \ + do { \ + ++JSCToolsTest::assertionsRun; \ + auto testActual = (actual); \ + auto testExpected = (expected); \ + if (!(testActual == testExpected)) { \ + ++JSCToolsTest::assertionsFailed; \ + dataLogLn("FAIL: ", message, ": got ", testActual, ", expected ", testExpected, \ + " at ", __FILE__, ":", __LINE__); \ + } \ + } while (0) + +#define TEST_ASSERT_HEX_EQ(actual, expected, message) \ + do { \ + ++JSCToolsTest::assertionsRun; \ + uint64_t testActual = (actual); \ + uint64_t testExpected = (expected); \ + if (testActual != testExpected) { \ + ++JSCToolsTest::assertionsFailed; \ + dataLogLn("FAIL: ", message, ": got 0x", hex(testActual), ", expected 0x", hex(testExpected), \ + " at ", __FILE__, ":", __LINE__); \ + } \ + } while (0) + +// The number of names in this task's Mach port name space. Used to show that a +// sequence of operations leaves no port behind. +unsigned machPortNameCount(); + +// The number of send rights this task holds for `port`, or 0 if it holds no name for +// it. A task that acquires a port it already has a name for gets another reference +// under that same name rather than a new name, so a right that is taken and never +// given back shows up here and not in machPortNameCount(). +unsigned machPortSendRightCount(mach_port_t); + +// Attaches to this process and takes a corpse of it. That is what lets a test +// check what a corpse reports against what this process already knows about +// itself, and it needs no privilege: a task may always snapshot itself. +// +// Reports the failure if either step does not work, so a caller only has to +// check isValid() and return. +class SelfSnapshot { +public: + SelfSnapshot(); + ~SelfSnapshot(); + + SelfSnapshot(const SelfSnapshot&) = delete; + SelfSnapshot& operator=(const SelfSnapshot&) = delete; + + bool isValid() const; + JSC::Corpse::Snapshot& snapshot() const; + RefPtr process() const; + +private: + RefPtr m_process; + std::unique_ptr m_snapshot; +}; + +// Threads that park themselves until stopped, each under a name of our choosing, +// so that a corpse of this process contains threads whose properties are known. +class ParkedThreads { +public: + struct Thread; + + // pthread keeps a thread name in a fixed buffer, so a longer name arrives cut + // to this length. + static constexpr size_t maximumNameLength = 63; + + ~ParkedThreads(); + + // Returns false if the thread could not be created. + bool spawn(const char* name); + + // Blocks until every spawned thread is parked, so that a snapshot taken + // afterwards sees them with their names set and their stacks in use. + bool waitUntilAllParked(); + + void stopAndJoin(); + + size_t count() const { return m_threads.size(); } + +private: + Vector m_threads; +}; + +} // namespace JSCToolsTest + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/corpse/tests/testLibJSCTools.cpp b/Source/JavaScriptCore/corpse/tests/testLibJSCTools.cpp new file mode 100644 index 000000000000..d6da8fecd368 --- /dev/null +++ b/Source/JavaScriptCore/corpse/tests/testLibJSCTools.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include "CorpseAddressTest.h" +#include "CorpseByteParserTest.h" +#include "CorpseExportsTrieTest.h" +#include "CorpseProcessTest.h" +#include "CorpseRegionTest.h" +#include "CorpseSnapshotTest.h" +#include "CorpseSymbolTest.h" +#include "CorpseThreadTest.h" +#include "LibJSCToolsTestUtilities.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +// A default that runs in well under a second, so that every run exercises the +// decoder on inputs nobody wrote. A longer hunt is a matter of passing a bigger +// count and a different seed. +constexpr uint64_t defaultFuzzSeed = 0x5eed1234; +constexpr unsigned defaultFuzzIterations = 20000; + +void printUsage() +{ + dataLogLn("Usage: testLibJSCTools []"); + dataLogLn(" testLibJSCTools --fuzz-trie [ []]"); + dataLogLn(""); + dataLogLn(" Runs the tests for libJavaScriptCoreTools. With a filter, only the"); + dataLogLn(" suites whose name contains it run."); +} + +bool parseUint64(std::string_view text, uint64_t& out) +{ + uint8_t base = text.starts_with("0x") || text.starts_with("0X") ? 16 : 10; + if (base == 16) + text = text.substr(2); + auto parsed = WTF::parseInteger(StringView::fromLatin1(std::string(text).c_str()), base); + if (!parsed) + return false; + out = *parsed; + return true; +} + +} // anonymous namespace + +int main(int argc, char** argv) +{ + uint64_t fuzzSeed = defaultFuzzSeed; + uint64_t fuzzIterations = defaultFuzzIterations; + bool fuzzOnly = false; + + // argv is wrapped in a span so that nothing here walks off the end of it. + auto arguments = unsafeMakeSpan(argv, static_cast(argc)); + for (size_t index = 1; index < arguments.size(); ++index) { + std::string_view argument = arguments[index]; + if (argument == "--help" || argument == "-h") { + printUsage(); + return 0; + } + if (argument == "--fuzz-trie") { + fuzzOnly = true; + if (index + 1 < arguments.size() && parseUint64(arguments[index + 1], fuzzSeed)) { + ++index; + if (index + 1 < arguments.size() && parseUint64(arguments[index + 1], fuzzIterations)) + ++index; + } + continue; + } + if (argument.starts_with("-")) { + dataLogLn("Unknown option '", arguments[index], "'"); + printUsage(); + return 1; + } + JSCToolsTest::suiteFilter = arguments[index]; + } + + dataLogLn("Starting libJavaScriptCoreTools tests"); + + if (fuzzOnly) + JSCToolsTest::fuzzExportsTrie(fuzzSeed, static_cast(fuzzIterations)); + else { + JSCToolsTest::testByteParser(); + JSCToolsTest::testExportsTrie(); + JSCToolsTest::fuzzExportsTrie(fuzzSeed, static_cast(fuzzIterations)); + JSCToolsTest::testAddress(); + JSCToolsTest::testProcess(); + JSCToolsTest::testSnapshot(); + JSCToolsTest::testRegion(); + JSCToolsTest::testThreads(); + JSCToolsTest::testSymbol(); + } + + dataLogLn("Ran ", JSCToolsTest::assertionsRun, " assertions, ", + JSCToolsTest::assertionsFailed, " failed, ", + JSCToolsTest::suitesSkipped, " suites skipped"); + + if (JSCToolsTest::assertionsFailed) { + dataLogLn("Some libJavaScriptCoreTools tests FAILED!"); + return 1; + } + if (!JSCToolsTest::assertionsRun) { + dataLogLn("No tests ran!"); + return 1; + } + + dataLogLn("All libJavaScriptCoreTools tests PASSED!"); + return 0; +} + +#else // libJavaScriptCoreTools support unavailable + +int main(int, char**) +{ + // The corpse support is built on Mach task APIs, so there is nothing to test + // on other platforms. Simulators and MacCatalyst are also not supported. + // Report success so that a run here is not a failure. + printf("Not supported platform for testLibJSCTools\n"); + return 0; +} + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) diff --git a/Source/JavaScriptCore/mya/mya.cpp b/Source/JavaScriptCore/mya/mya.cpp new file mode 100644 index 000000000000..1ee8ccd46547 --- /dev/null +++ b/Source/JavaScriptCore/mya/mya.cpp @@ -0,0 +1,1407 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE(READLINE) +// readline/history.h has a Function typedef that conflicts with WTF::Function; +// rename it across these includes to avoid the clash. +#define Function ReadlineFunction +#include +#include +#undef Function +#endif + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN + +using JSC::Corpse::Address; +using JSC::Corpse::Process; +using JSC::Corpse::Snapshot; +using JSC::Corpse::Thread; + +namespace Mya { + +// A lexer over a null-terminated string. Parsing methods skip leading +// whitespace and advance past whatever they consume; a failed parse leaves the +// position where it was. A Lexer is essentially made up of a position in the +// string. So copying one is how you look ahead without committing. +class Lexer { +public: + explicit Lexer(const char* text) + : m_at(text) + { + } + + void skipWhitespace() + { + while (isTabOrSpace(*m_at)) + ++m_at; + } + + // True if only whitespace remains. + bool atEnd() + { + skipWhitespace(); + return !*m_at; + } + + // The next non-whitespace character, or '\0' at end of input. + char peek() + { + skipWhitespace(); + return *m_at; + } + + // Consumes and returns the next whitespace-delimited token, which is empty + // at end of input. + std::string_view nextToken() + { + skipWhitespace(); + const char* start = m_at; + while (*m_at && !isTabOrSpace(*m_at)) + ++m_at; + return std::string_view(start, static_cast(m_at - start)); + } + + // Consumes the next token only if it matches word. + bool consumeToken(const char* word) + { + Lexer probe = *this; + if (probe.nextToken() != word) + return false; + *this = probe; + return true; + } + + // Consumes the next non-whitespace character only if it matches c. + bool consumeChar(char c) + { + skipWhitespace(); + if (*m_at != c) + return false; + ++m_at; + return true; + } + + // Consumes a positive number from the specified `minimum` upwards, but capped at INT_MAX. + template + bool consumeUint32(T& out) + { + skipWhitespace(); + if (!isASCIIDigit(*m_at)) + return false; // Rejects cases like -0, -1, +3, which strtol allows. + errno = 0; + char* end = nullptr; + long value = strtol(m_at, &end, 10); + if (end == m_at || errno || value < minimum || value > INT_MAX) + return false; + m_at = end; + out = static_cast(value); + return true; + } + + bool consumePID(pid_t& pid) { return consumeUint32<1>(pid); } + +private: + const char* m_at; +}; + +class Shell { +public: + ~Shell() + { + cleanup(); + } + + int run(int argc, char** argv) + { + JSC::Corpse::Client::setName("mya"_s); + auto action = parseArguments(argc, argv); + switch (action) { + case ContinuationAction::Continue: + openHistory(); + runInteractive(); + return 0; + case ContinuationAction::Exit: + return 0; + case ContinuationAction::Error: + return 1; + } + RELEASE_ASSERT_NOT_REACHED(); + return 1; + } + +private: + static constexpr const char* prompt = ">>> "; + static constexpr const char* historyDirName = ".mya"; + static constexpr const char* historyFileName = "history"; + static constexpr unsigned defaultMaxHistoryEntries = 50; + static constexpr unsigned minHistorySize = 5; + + // The history file records the target max history entries in its first line, followed by + // historical commands. To avoid re-writing the file on every new command, we allow the file + // to exceed the max entries by maxOverflowEntries, before we do a re-write to purge + // the extra entries. We will keep appending to the same file until the re-write is needed. + static constexpr const char* maxEntriesHeaderPrefix = "max entries "; + static constexpr unsigned maxOverflowEntries = 100; + + static void printUsage(FILE* out) + { + fputs("Mya (MY-uh /ˈmaɪə/) - MemorY Analyzer\n", out); + fputs("Usage:\n", out); + fputs(" mya [--pid|-p ]\n", out); + fputs(" mya [--help|-h []]\n", out); + fputs("Commands:\n", out); + fputs(" attach [--pid|-p] Set the target PID and attach\n", out); + fputs(" detach Detach from the current PID\n", out); + fputs(" status (st) Show whether mya is attached\n", out); + fputs(" snapshot (sn, snap) ... Capture and manage snapshots\n", out); + fputs(" thread (th) ... Inspect the threads in a snapshot\n", out); + fputs(" p[/x] & Print a symbol's address, /x for hex\n", out); + fputs(" history (hi, hist) ... Show and manage the command history\n", out); + fputs(" help [] Show this help, or help for \n", out); + fputs(" quit (q, exit) Exit mya\n", out); + fputs("\n", out); + fputs(" Use `help snapshot`, `help thread` or `help history` for their subcommands.\n", out); + fputs("\n", out); + } + + static void printThreadUsage(FILE* out) + { + fputs("thread - inspect the threads captured in a snapshot\n", out); + fputs(" thread list (li) List the threads in the snapshot in use\n", out); + fputs("\n", out); + fputs(" `thread` may be abbreviated as `th`, and lists by default.\n", out); + fputs(" Threads are read from the snapshot in use; see `help snapshot`.\n", out); + fputs("\n", out); + } + + static void printSnapshotUsage(FILE* out) + { + fputs("snapshot - capture and manage snapshots of a process\n", out); + fputs(" snapshot Capture a snapshot of the current process\n", out); + fputs(" snapshot --pid|-p Attach to and capture a snapshot of it\n", out); + fputs(" snapshot Switch to using snapshot \n", out); + fputs(" snapshot list (li) List captured snapshots (* marks the one in use)\n", out); + fputs(" snapshot info (inf) Show details of snapshot \n", out); + fputs(" snapshot delete (del) Delete snapshot \n", out); + fputs(" snapshot diff Diff snapshot against snapshot \n", out); + fputs("\n", out); + fputs(" `snapshot` may be abbreviated as `sn` or `snap`.\n", out); + fputs(" Capturing a snapshot switches to using it.\n", out); + fputs("\n", out); + } + + static void printHistoryUsage(FILE* out) + { + fputs("history - show and manage the command history\n", out); + fputs(" history List the command history\n", out); + fputs(" history clear [] Clear the history, or its oldest entries\n", out); + fputs(" history size [] Show or set the max entries kept\n", out); + fputs(" ! Replay history entry \n", out); + fputs(" !! Replay the previous command\n", out); + fputs("\n", out); + fputs(" `history` may be abbreviated as `hi` or `hist`.\n", out); + fprintf(out, " Command history is kept (defaults up to %u entries) in ~/%s/%s.\n", + defaultMaxHistoryEntries, historyDirName, historyFileName); +#if HAVE(READLINE) + fputs(" It is navigable with the Up/Down arrows and Ctrl-R reverse search.\n", out); +#endif + fputs("\n", out); + } + + // Dispatches `help []`. `lex` is positioned after the "help" word. + static bool handleHelp(Lexer lex) + { + if (lex.atEnd()) { + printUsage(stdout); + return true; + } + std::string_view topic = lex.nextToken(); + if (topic == "sn" || topic == "snap" || topic == "snapshot") { + printSnapshotUsage(stdout); + return true; + } + if (topic == "hi" || topic == "hist" || topic == "history") { + printHistoryUsage(stdout); + return true; + } + if (topic == "th" || topic == "thread") { + printThreadUsage(stdout); + return true; + } + fprintf(stderr, "mya: No help for '%.*s'\n", static_cast(topic.length()), topic.data()); + return false; + } + + // Writes a byte count in the largest unit that keeps it readable, e.g. "512 KB" or "1.50 MB". + static void formatByteSize(size_t bytes, char* out, size_t outSize) + { + if (bytes >= 1024 * 1024) + snprintf(out, outSize, "%.2f MB", bytes / (1024.0 * 1024.0)); + else if (bytes >= 1024) + snprintf(out, outSize, "%zu KB", bytes / 1024); + else + snprintf(out, outSize, "%zu B", bytes); + } + + enum class ContinuationAction { Continue, Exit, Error }; + ContinuationAction parseArguments(int argc, char** argv) + { + // Help is answered before anything else is acted on, so that asking for it + // never attaches to a process or takes a snapshot along the way. + for (int i = 1; i < argc; ++i) { + std::string_view arg = argv[i]; + if (arg != "--help" && arg != "-h") + continue; + if (i + 1 >= argc) { + printUsage(stdout); + return ContinuationAction::Exit; + } + Lexer lex(argv[i + 1]); + return handleHelp(lex) ? ContinuationAction::Exit : ContinuationAction::Error; + } + + for (int i = 1; i < argc; ++i) { + const char* argText = argv[i]; + std::string_view arg = argText; + const char* pidText = nullptr; + if (arg == "--pid" || arg == "-p") { + if (i + 1 >= argc) { + fprintf(stderr, "mya: %s requires an argument\n", argText); + return ContinuationAction::Error; + } + pidText = argv[++i]; + } else if (arg.starts_with("--pid=")) + pidText = argText + 6; + else if (arg.starts_with("-p") && arg.size() > 2) + pidText = argText + 2; // "-p12345" + else { + fprintf(stderr, "mya: Unknown option '%s'\n", argText); + return ContinuationAction::Error; + } + + pid_t pid = -1; + Lexer lex(pidText); + if (!lex.consumePID(pid) || !lex.atEnd()) { + fprintf(stderr, "mya: Invalid PID '%s'\n", pidText); + return ContinuationAction::Error; + } + + attachAndSnapshot(pid); + } + return ContinuationAction::Continue; + } + + void attach(pid_t pid) + { + RefPtr process; + auto existing = m_processes.find(pid); + if (existing != m_processes.end()) + process = existing->value; + else { + process = Process::create(pid); + m_processes.add(pid, process); + } + + if (!process->attach()) + return; + + if (m_currentProcess && m_currentProcess != process) + m_currentProcess->detach(); + + m_currentProcess = WTF::move(process); + printf("Attached to %d\n", static_cast(m_currentProcess->pid())); + } + + void detach() + { + if (!m_currentProcess) { + fputs("Not attached to any process.\n", stdout); + return; + } + pid_t pid = m_currentProcess->pid(); + m_currentProcess->detach(); + m_currentProcess = nullptr; + printf("Detached from %d\n", static_cast(pid)); + } + + // `mya --pid ` and `snapshot --pid ` both attach then snapshot. + void attachAndSnapshot(pid_t pid) + { + attach(pid); + if (m_currentProcess && m_currentProcess->pid() == pid) + captureSnapshot(); + } + + void captureSnapshot() + { + if (!m_currentProcess) { + fputs("Unable to capture snapshot. Not attached to any process. Use `attach` command or specify `--pid` argument for the snapshot command.\n", stderr); + return; + } + auto snapshot = WTF::makeUnique(m_currentProcess); + if (!snapshot->isValid()) + return; // The Snapshot constructor already logged the failure. + unsigned id = snapshot->id(); + // The map owns the Snapshot and the list only records capture order. + Snapshot* node = snapshot.get(); + m_snapshotsById.add(id, WTF::move(snapshot)); + m_snapshots.append(node); + printf("Captured Snapshot #%u of %d\n", id, static_cast(m_currentProcess->pid())); + useSnapshot(id); // Capturing switches to the new snapshot. + } + + // Sets the current snapshot used by subsequent commands. + void useSnapshot(unsigned id) + { + if (!snapshotById(id)) { + fprintf(stderr, "mya: No snapshot #%u\n", id); + return; + } + if (m_currentSnapshot) { + if (m_currentSnapshot == id) + printf("Already using snapshot %u\n", id); + else + printf("Switching to using snapshot %u\n", id); + } + m_currentSnapshot = id; + } + + // Returns the snapshot with the given id, or nullptr if there is none. + Snapshot* snapshotById(unsigned id) const + { + auto entry = m_snapshotsById.find(id); + return entry != m_snapshotsById.end() ? entry->value.get() : nullptr; + } + + void listSnapshots() + { + if (m_snapshots.isEmpty()) { + fputs("No snapshots.\n", stdout); + return; + } + for (Snapshot* snapshot = m_snapshots.head(); snapshot; snapshot = snapshot->next()) { + // Mark the snapshot currently in use. + const char* marker = snapshot->id() == m_currentSnapshot ? "*" : " "; + printf("%s #%u: pid %d\n", marker, snapshot->id(), static_cast(snapshot->process()->pid())); + } + } + + void snapshotInfo(unsigned id) + { + Snapshot* snapshot = snapshotById(id); + if (!snapshot) { + fprintf(stderr, "mya: No snapshot #%u\n", id); + return; + } + printf("Snapshot #%u: pid %d, corpse %s\n", id, + static_cast(snapshot->process()->pid()), snapshot->isValid() ? "valid" : "invalid"); + } + + void snapshotDelete(unsigned id) + { + Snapshot* snapshot = snapshotById(id); + if (!snapshot) { + fprintf(stderr, "mya: No snapshot #%u\n", id); + return; + } + // Unlink before dropping the owning entry: the list does not own its + // nodes, so it must not be left pointing at a destroyed Snapshot. + m_snapshots.remove(snapshot); + m_snapshotsById.remove(id); + if (id == m_currentSnapshot) + m_currentSnapshot = 0; + printf("Deleted Snapshot #%u.\n", id); + } + + void snapshotDiff(unsigned a, unsigned b) + { + if (!snapshotById(a)) { + fprintf(stderr, "mya: No snapshot #%u\n", a); + return; + } + if (!snapshotById(b)) { + fprintf(stderr, "mya: No snapshot #%u\n", b); + return; + } + printf("Snapshot diff #%u vs #%u is not implemented yet.\n", a, b); + } + + // `lex` is positioned after the "snapshot" command word. + void handleSnapshot(Lexer lex) + { + if (lex.atEnd()) { + captureSnapshot(); + return; + } + // A bare number switches to that snapshot e.g. "snapshot 3". + if (isASCIIDigit(static_cast(lex.peek()))) { + unsigned number = 0; + if (!lex.consumeUint32(number) || !lex.atEnd()) { + fputs("Usage: snapshot \n", stderr); + return; + } + useSnapshot(number); + return; + } + if (lex.consumeToken("--pid") || lex.consumeToken("-p")) { + pid_t pid = -1; + if (!lex.consumePID(pid) || !lex.atEnd()) { + fputs("Usage: snapshot [--pid|-p] \n", stderr); + return; + } + attachAndSnapshot(pid); + return; + } + if (lex.consumeToken("li") || lex.consumeToken("list")) { + listSnapshots(); + return; + } + if (lex.consumeToken("inf") || lex.consumeToken("info")) { + unsigned number = 0; + if (!lex.consumeUint32(number) || !lex.atEnd()) { + fputs("Usage: snapshot info \n", stderr); + return; + } + snapshotInfo(number); + return; + } + if (lex.consumeToken("del") || lex.consumeToken("delete")) { + unsigned number = 0; + if (!lex.consumeUint32(number) || !lex.atEnd()) { + fputs("Usage: snapshot delete \n", stderr); + return; + } + snapshotDelete(number); + return; + } + if (lex.consumeToken("diff")) { + unsigned a = 0; + unsigned b = 0; + if (!lex.consumeUint32(a) || !lex.consumeUint32(b) || !lex.atEnd()) { + fputs("Usage: snapshot diff \n", stderr); + return; + } + snapshotDiff(a, b); + return; + } + std::string_view token = lex.nextToken(); + fprintf(stderr, "mya: Unknown snapshot subcommand '%.*s'\n", + static_cast(token.length()), token.data()); + } + + // Lists the threads captured in the snapshot currently in use. + void listThreads() + { + Snapshot* snapshot = snapshotById(m_currentSnapshot); + if (!snapshot) { + fputs("No snapshot in use. Capture one with `snapshot`, or select one with `snapshot `.\n", stderr); + return; + } + + const Vector& threads = snapshot->threads(); + if (threads.isEmpty()) { + fputs("No threads.\n", stdout); + return; + } + + printf("Threads in snapshot #%u (pid %d):\n", snapshot->id(), static_cast(snapshot->process()->pid())); + + // Build the rows as text first so each column can be sized to its widest entry. + static constexpr size_t columnCount = 12; + static const char* const headings[columnCount] = { + "INDEX", "TID", "STATE", "USER(ms)", "SYS(ms)", "SP", "STACK", "SIZE", + "PAGES", "RESIDENT", "DIRTY", "NAME" + }; + static const bool rightAligned[columnCount] = { + true, false, false, true, true, false, false, true, true, true, true, false + }; + + struct Row { + std::string cells[columnCount]; + }; + Vector rows; + rows.reserveCapacity(threads.size()); + + char buffer[64]; + for (size_t i = 0; i < threads.size(); ++i) { + const Thread& thread = threads[i]; + Row row; + + snprintf(buffer, sizeof(buffer), "%zu", i + 1); + row.cells[0] = buffer; + snprintf(buffer, sizeof(buffer), "0x%llx", static_cast(thread.id())); + row.cells[1] = buffer; + row.cells[2] = thread.runStateDescription(); + snprintf(buffer, sizeof(buffer), "%.3f", thread.userTimeUsec() / 1000.0); + row.cells[3] = buffer; + snprintf(buffer, sizeof(buffer), "%.3f", thread.systemTimeUsec() / 1000.0); + row.cells[4] = buffer; + if (thread.stackPointer()) { + snprintf(buffer, sizeof(buffer), "0x%llx", + thread.stackPointer().toMachVMAddress()); + row.cells[5] = buffer; + } else + row.cells[5] = "-"; + if (thread.hasStack()) { + const auto& stack = thread.stackRegion(); + snprintf(buffer, sizeof(buffer), "0x%llx-0x%llx", + stack.base().toMachVMAddress(), + stack.end().toMachVMAddress()); + row.cells[6] = buffer; + formatByteSize(stack.size(), buffer, sizeof(buffer)); + row.cells[7] = buffer; + snprintf(buffer, sizeof(buffer), "%llu", + static_cast(stack.pageCount())); + row.cells[8] = buffer; + snprintf(buffer, sizeof(buffer), "%llu", + static_cast(stack.residentPageCount())); + row.cells[9] = buffer; + snprintf(buffer, sizeof(buffer), "%llu", + static_cast(stack.dirtyPageCount())); + row.cells[10] = buffer; + } else { + row.cells[6] = "-"; + row.cells[7] = "-"; + row.cells[8] = "-"; + row.cells[9] = "-"; + row.cells[10] = "-"; + } + row.cells[11] = thread.name().empty() ? "-" : thread.name(); + + rows.append(WTF::move(row)); + } + + size_t widths[columnCount]; + for (size_t column = 0; column < columnCount; ++column) { + widths[column] = strlen(headings[column]); + for (const Row& row : rows) + widths[column] = std::max(widths[column], row.cells[column].length()); + } + + auto printRow = [&](auto&& cellAt) { + fputs(" ", stdout); + for (size_t column = 0; column < columnCount; ++column) { + if (column) + fputs(" ", stdout); + const char* text = cellAt(column); + // The last column needs no padding, which also avoids trailing + // whitespace on every line. + if (column == columnCount - 1) + fputs(text, stdout); + else if (rightAligned[column]) + printf("%*s", static_cast(widths[column]), text); + else + printf("%-*s", static_cast(widths[column]), text); + } + putchar('\n'); + }; + + printRow([&](size_t column) { return headings[column]; }); + for (const Row& row : rows) + printRow([&](size_t column) { return row.cells[column].c_str(); }); + } + + // Dispatches the `thread ...` subcommands. `lex` is positioned after the + // "thread" command word. + void handleThread(Lexer lex) + { + // Listing is the default, so a bare `thread` lists too. + if (lex.atEnd() || lex.consumeToken("li") || lex.consumeToken("list")) { + if (!lex.atEnd()) { + fputs("Usage: thread list\n", stderr); + return; + } + listThreads(); + return; + } + std::string_view token = lex.nextToken(); + fprintf(stderr, "mya: Unknown thread subcommand '%.*s'\n", + static_cast(token.length()), token.data()); + } + + // Dispatches `p[/] `. The only expression understood so + // far is `&`, which resolves the symbol in the snapshot in use. + // `format` is the text after the '/', empty when none was given. + void handlePrint(std::string_view format, Lexer lex) + { + bool hex = false; + if (!format.empty()) { + if (format == "x") + hex = true; + else if (format != "d") { + fprintf(stderr, "mya: Unknown print format '%.*s'; use x or d\n", + static_cast(format.length()), format.data()); + return; + } + } + + Snapshot* snapshot = snapshotById(m_currentSnapshot); + if (!snapshot) { + fputs("No snapshot in use. Capture one with `snapshot`, or select one with `snapshot `.\n", stderr); + return; + } + + // Taking a symbol's address is all we can do without type information. + if (!lex.consumeChar('&')) { + fputs("Usage: p[/x] &\n", stderr); + return; + } + std::string_view token = lex.nextToken(); + if (token.empty() || !lex.atEnd()) { + fputs("Usage: p[/x] &\n", stderr); + return; + } + + std::string name(token); + Address address = snapshot->symbol(name.c_str()); + if (!address) { + fprintf(stderr, "mya: No symbol '%s' in snapshot #%u\n", name.c_str(), snapshot->id()); + return; + } + if (hex) + printf("&%s = 0x%llx\n", name.c_str(), address.toMachVMAddress()); + else + printf("&%s = %llu\n", name.c_str(), address.toMachVMAddress()); + } + + // Releases resources without extra output. Dropping the current selection + // and clearing the containers runs ~Process / ~Snapshot, which release the + // task and corpse ports. Idempotent: safe from the quit path and destructor. + void cleanup() + { + m_currentProcess = nullptr; + m_processes.clear(); + // Unlink the non-owning list before destroying the Snapshots it points at. + m_snapshots.clear(); + m_snapshotsById.clear(); + if (m_historyFile) { + fclose(m_historyFile); + m_historyFile = nullptr; + } + if (m_historyDirDescriptor >= 0) { + close(m_historyDirDescriptor); + m_historyDirDescriptor = -1; + } + } + + // Prompts for confirmation before quitting. Enter (empty) defaults to yes. + bool confirmQuit() + { + for (;;) { + std::string response; +#if HAVE(READLINE) + char* input = readline("Really quit? [Y/n] "); + if (!input) { + putchar('\n'); + return true; // EOF: treat as yes. + } + response = input; + free(input); +#else + fputs("Really quit? [Y/n] ", stdout); + fflush(stdout); + char buffer[64]; + if (!fgets(buffer, sizeof(buffer), stdin)) { + putchar('\n'); + return true; // EOF: treat as yes. + } + // Without a newline the answer was longer than the buffer, and the rest + // would be read as the answer to the next prompt. Discard it. + if (!std::string_view(buffer).contains('\n')) { + int discarded = 0; + while ((discarded = getchar()) != '\n' && discarded != EOF) { } + } + response = buffer; +#endif + size_t start = 0; + while (start < response.size() && isASCIIWhitespace(static_cast(response[start]))) + ++start; + size_t stop = response.size(); + while (stop > start && isASCIIWhitespace(static_cast(response[stop - 1]))) + --stop; + response = response.substr(start, stop - start); + + if (response.empty() || response[0] == 'y' || response[0] == 'Y') + return true; + if (response[0] == 'n' || response[0] == 'N') + return false; + fputs("Please answer 'y' or 'n'.\n", stdout); + } + } + + void printStatus() + { + if (m_currentProcess) + printf("Attached to pid %d\n", static_cast(m_currentProcess->pid())); + else + fputs("Not attached to any process.\n", stdout); + + if (Snapshot* snapshot = snapshotById(m_currentSnapshot)) + printf("Using snapshot %u of pid %d\n", snapshot->id(), static_cast(snapshot->process()->pid())); + else + fputs("No snapshot in use.\n", stdout); + } + + void printHistory() + { + if (!m_history.size()) { + printf("History is empty.\n"); + return; + } + for (size_t i = 0; i < m_history.size(); ++i) + printf("%5zu %s\n", i + 1, m_history[i].c_str()); + } + + // Drops the `count` oldest entries. + void clearHistory(unsigned count) + { + if (!count) { + fputs("Nothing to do for clearing 0 history entries.\n", stdout); + return; + } + if (m_history.empty()) { + fputs("History is already empty.\n", stdout); + return; + } + unsigned removeCount = count >= m_history.size() ? safeCast(m_history.size()) : count; + m_history.erase(m_history.begin(), m_history.begin() + removeCount); +#if HAVE(READLINE) + // readline has no way to drop individual entries, so rebuild its history + // from the cache to keep the arrow keys in sync. + clear_history(); + for (const std::string& command : m_history) + add_history(command.c_str()); +#endif + if (m_historyFile && !rewriteHistoryFile()) { + fputs("mya: Failed to clear history file.\n", stderr); + return; + } + if (m_history.empty()) { + if (removeCount == 1) + printf("Cleared 1 history entry.\n"); + else + printf("Cleared %u history entries.\n", removeCount); + } else if (removeCount == 1) + printf("Cleared the oldest history entry.\n"); + else + printf("Cleared the %u oldest history entries.\n", removeCount); + } + + void printHistorySize() + { + printf("History holds %zu of %u entries.\n", m_history.size(), m_maxHistoryEntries); + } + + // Sets how many entries the history keeps, purging the oldest if the new + // capacity is smaller than what is currently stored. + void setMaxHistorySize(unsigned capacity) + { + if (capacity < minHistorySize) { + capacity = minHistorySize; + printf("Minimum history size is %u.\n", minHistorySize); + } + if (m_maxHistoryEntries == capacity) { + printf("Maximum history size is already %u.\n", m_maxHistoryEntries); + return; + } + m_maxHistoryEntries = capacity; + boundReadlineHistory(); + if (m_history.size() > m_maxHistoryEntries) { + m_history.erase(m_history.begin(), m_history.end() - m_maxHistoryEntries); +#if HAVE(READLINE) + clear_history(); + for (const std::string& command : m_history) + add_history(command.c_str()); +#endif + } + if (!rewriteHistoryFile()) + fputs("mya: The new size applies to this session only.\n", stderr); + printHistorySize(); + } + + // Dispatches the `history ...` subcommands. `lex` is positioned after the + // "history" command word. + void handleHistory(Lexer lex) + { + if (lex.atEnd()) { + printHistory(); + return; + } + if (lex.consumeToken("clear")) { + unsigned count = UINT_MAX; // Default to "all". + if (!lex.atEnd()) { + unsigned parsed = 0; + if (!lex.consumeUint32(parsed) || !lex.atEnd()) { + fputs("Usage: history clear []\n", stderr); + return; + } + count = parsed; + } + clearHistory(count); + return; + } + if (lex.consumeToken("size")) { + if (lex.atEnd()) { + printHistorySize(); + return; + } + unsigned capacity = 0; + if (!lex.consumeUint32(capacity) || !lex.atEnd()) { + fputs("Usage: history size []\n", stderr); + return; + } + setMaxHistorySize(capacity); + return; + } + std::string_view token = lex.nextToken(); + fprintf(stderr, "mya: Unknown history subcommand '%.*s'\n", + static_cast(token.length()), token.data()); + } + + // Resolves a history reference ("!!" or "!") to a stored command and + // replays it. `lex` is positioned after the leading '!'; `line` is the whole + // input, used for error reporting. + void replayHistory(const char* line, Lexer lex) + { + std::string command; + if (lex.consumeChar('!') && lex.atEnd()) { + if (m_history.empty()) { + fputs("mya: No commands in history\n", stderr); + return; + } + command = m_history.back(); + } else { + unsigned index = 0; + if (!lex.consumeUint32(index) || !lex.atEnd() || index > m_history.size()) { + fprintf(stderr, "mya: %s: event not found\n", line); + return; + } + if (!index) { + fprintf(stderr, "mya: %s: invalid history entry\n", line); + return; + } + command = m_history[index - 1]; + } + // Echo the resolved command, then run it as if it had been typed. The + // replayed command records itself; the "!" reference is not recorded. + printf("%s\n", command.c_str()); + handleLine(command.c_str()); + } + + static bool isRunningAsRoot() { return !geteuid(); } + + // The directory that holds the history file, empty if the user has no home + // directory to put it in. + // + // We deliberately keep root (when run with sudo)'s history file distinct from + // the non-root user's. This is better for security (root is not dependent on + // non-root user data), and does not block the non-root user from accessing + // their history if the last mya run was via sudo and the history file was + // updated by root (and ownership changed). + static std::string historyDirectory() + { + const char* home = nullptr; + if (isRunningAsRoot()) { + if (const struct passwd* entry = getpwuid(0)) + home = entry->pw_dir; + } else { + home = getenv("HOME"); + if (!home || !*home) { + if (const struct passwd* entry = getpwuid(getuid())) + home = entry->pw_dir; + } + } + if (!home || !*home) + return { }; + + std::string directory = home; + if (directory.back() != '/') + directory += '/'; + directory += historyDirName; + return directory; + } + + void boundReadlineHistory() + { +#if HAVE(READLINE) + // libedit only applies the bound when an entry is added, so lowering it does + // not shorten the existing list: callers that shrink the cache must rebuild + // readline's list as well for the change to take effect immediately. + stifle_history(safeCast(m_maxHistoryEntries)); +#endif + } + + // Opens the history file for read+write, creating it if absent, and loads any + // stored commands into the cache. If it cannot be opened, the cache stays in + // memory only for the session. + // + // The file lives under the user's home directory, not the working directory: + // mya carries a debugger entitlement and its own usage suggests running it as + // root, so it must not be steered into writing through a path controlled by + // whoever owns the directory it happens to be started in. Both path + // components are opened O_NOFOLLOW, so a symlink planted at either one is + // refused rather than followed, and the file is never opened with O_TRUNC. + void openHistory() + { + boundReadlineHistory(); + + std::string directory = historyDirectory(); + if (directory.empty()) { + fputs("mya: No home directory, so command history will not be saved.\n", stderr); + return; + } + + if (isRunningAsRoot()) { + fprintf(stderr, "mya: Running as root: using history file %s/%s.\n", + directory.c_str(), historyFileName); + } + + if (mkdir(directory.c_str(), 0700) && errno != EEXIST) { + fprintf(stderr, "mya: Could not create %s: %s\n", directory.c_str(), strerror(errno)); + return; + } + + int directoryDescriptor = open(directory.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (directoryDescriptor < 0) { + fprintf(stderr, "mya: Could not open %s: %s\n", directory.c_str(), strerror(errno)); + return; + } + + int fileDescriptor = openat(directoryDescriptor, historyFileName, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0600); + if (fileDescriptor < 0) { + fprintf(stderr, "mya: Could not open %s/%s: %s\n", directory.c_str(), historyFileName, strerror(errno)); + close(directoryDescriptor); + return; + } + + // Anything other than a regular file is not something mya wrote: reading a + // FIFO here would block the shell before it ever prompted. So, we decline to open + // any non-regular files. + struct stat status; + if (fstat(fileDescriptor, &status) || !S_ISREG(status.st_mode)) { + fprintf(stderr, "mya: %s/%s is not a regular file, so command history will not be saved.\n", + directory.c_str(), historyFileName); + close(fileDescriptor); + close(directoryDescriptor); + return; + } + + m_historyFile = fdopen(fileDescriptor, "r+"); + if (!m_historyFile) { + close(fileDescriptor); + close(directoryDescriptor); + return; + } + + // Held for the session: rewriting the file creates and renames through this + // descriptor, so the replacement lands in the directory that was checked + // here rather than wherever the path may point by then. + m_historyDirDescriptor = directoryDescriptor; + + loadHistory(); + } + + // Reads the cap out of a "max entries " header line. Returns 0 if `line` does not + // contain the header, which means the file is corrupted. + static unsigned parseMaxEntriesHeader(const char* line) + { + size_t prefixLength = strlen(maxEntriesHeaderPrefix); + if (!std::string_view(line).starts_with(maxEntriesHeaderPrefix)) + return 0; + Lexer lex(line + prefixLength); + unsigned entries = 0; + if (!lex.consumeUint32<1>(entries) || !lex.atEnd()) + return 0; + // We deliberately allow reading a capacity value below minHistorySize so that we can + // print a meaningful error message about it in the caller. + return entries; + } + + void loadHistory() + { + if (!m_historyFile) + return; + rewind(m_historyFile); + + char buffer[4096]; + unsigned capacity = 0; + if (fgets(buffer, sizeof(buffer), m_historyFile)) { + buffer[strcspn(buffer, "\n")] = '\0'; + capacity = parseMaxEntriesHeader(buffer); + if (!capacity) { + fprintf(stderr, "mya: Corrupted file: ~/%s/%s does not start with a valid header (\"%s\"); starting a new one.\n", + historyDirName, historyFileName, maxEntriesHeaderPrefix); + } else if (capacity < minHistorySize) { + fprintf(stderr, "mya: Corrupted file: ~/%s/%s header asks for fewer than the minimum %u entries;" + " starting a new one.\n", historyDirName, historyFileName, minHistorySize); + capacity = 0; // Treat as error. + } + } + if (!capacity) { + rewriteHistoryFile(); // Invalid header. Reset the history file. + return; + } + m_maxHistoryEntries = capacity; + boundReadlineHistory(); + + Vector entries; + while (fgets(buffer, sizeof(buffer), m_historyFile)) { + buffer[strcspn(buffer, "\n")] = '\0'; + if (!buffer[0]) + continue; + if (isReplayCommand(buffer)) + continue; // A ! replay command in history is invalid and not allowed. Skip. + entries.append(buffer); + } + m_entriesInHistoryFile = safeCast(entries.size()); + + // The cache never holds more than the capacity, however much the file holds. + unsigned keep = std::min(m_entriesInHistoryFile, m_maxHistoryEntries); + for (size_t i = entries.size() - keep; i < entries.size(); ++i) { + m_history.push_back(entries[i]); +#if HAVE(READLINE) + add_history(entries[i].c_str()); +#endif + } + + // Seek to the end so later commands append, and to satisfy the C rule + // that a positioning call separates a read from a following write. + if (fseek(m_historyFile, 0, SEEK_END)) + fallBackToMemoryOnly("Could not read the history file", errno); + } + + // Report the failure condition and switch to in-memory cache only history. + // The history file itself is left exactly as it was, and whatever was already + // read from it stays in the cache. + void fallBackToMemoryOnly(const char* what, int error) + { + fprintf(stderr, "mya: %s: %s\n", what, strerror(error)); + fputs("mya: Command history is kept in memory only from here, and the saved" + " history is left as it is.\n", stderr); + if (m_historyFile) { + fclose(m_historyFile); + m_historyFile = nullptr; + } + if (m_historyDirDescriptor >= 0) { + close(m_historyDirDescriptor); + m_historyDirDescriptor = -1; + } + } + + // Replaces the history file as a transaction i.e. the file either has the new + // history or remains the old one if something went wrong. It is never left half + // modified. This is done by writing the new file completely before replacing the + // old history file with it. + // + // In the event something went wrong, the in-memory cache retains its state, and + // may become out of sync with the history file. + bool rewriteHistoryFile() + { + if (!m_historyFile || m_historyDirDescriptor < 0) + return false; + + // Qualified by pid so two mya instances cannot land on the same temporary. + char temporaryName[64]; + snprintf(temporaryName, sizeof(temporaryName), "%s.%d.tmp", historyFileName, + static_cast(getpid())); + + int descriptor = openat(m_historyDirDescriptor, temporaryName, + O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600); + if (descriptor < 0 && errno == EEXIST) { + // Left behind by a run that died between creating and renaming. + unlinkat(m_historyDirDescriptor, temporaryName, 0); + descriptor = openat(m_historyDirDescriptor, temporaryName, + O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600); + } + if (descriptor < 0) { + fallBackToMemoryOnly("Could not create a temporary history file", errno); + return false; + } + + FILE* replacement = fdopen(descriptor, "w+"); + if (!replacement) { + int error = errno; + close(descriptor); + unlinkat(m_historyDirDescriptor, temporaryName, 0); + fallBackToMemoryOnly("Could not rewrite the history file", error); + return false; + } + + fprintf(replacement, "%s%u\n", maxEntriesHeaderPrefix, m_maxHistoryEntries); + for (const std::string& command : m_history) + fprintf(replacement, "%s\n", command.c_str()); + + // Commit the contents before publishing them, so a crash cannot leave the + // rename pointing at a file that was never written. + int error = 0; + if (fflush(replacement) || fsync(fileno(replacement))) + error = errno; + else if (ferror(replacement)) + error = EIO; + if (!error && renameat(m_historyDirDescriptor, temporaryName, m_historyDirDescriptor, historyFileName)) + error = errno; + if (error) { + fclose(replacement); + unlinkat(m_historyDirDescriptor, temporaryName, 0); + fallBackToMemoryOnly("Could not rewrite the history file", error); + return false; + } + + // The rename published the temporary file, so it is the history file now. + fclose(m_historyFile); + m_historyFile = replacement; + m_entriesInHistoryFile = safeCast(m_history.size()); + if (fseek(m_historyFile, 0, SEEK_END)) { + fallBackToMemoryOnly("Could not rewrite the history file", errno); + return false; + } + return true; + } + + static bool isReplayCommand(const char* line) + { + Lexer lex(line); + return lex.peek() == '!'; + } + + void recordCommand(const char* line) + { + // Do not allow replay commands in the history. They just pollute the history, and + // add recursion complexities in the replay execution code, which we want to prevent. + if (isReplayCommand(line)) + return; + + // Only filter an immediate repeat of the previous command. + if (!m_history.empty() && m_history.back() == line) + return; + + m_history.push_back(line); +#if HAVE(READLINE) + add_history(line); +#endif + if (m_history.size() > m_maxHistoryEntries) + m_history.erase(m_history.begin()); + + if (!m_historyFile) + return; + + fprintf(m_historyFile, "%s\n", line); + int error = 0; + if (fflush(m_historyFile)) + error = errno; + else if (ferror(m_historyFile)) + error = EIO; + if (error) { + fallBackToMemoryOnly("Could not append to the history file", error); + return; + } + ++m_entriesInHistoryFile; + // The sum cannot overflow: the capacity only ever comes from Lexer::consumeUint32, + // which rejects anything above INT_MAX, leaving room for the overflow + // allowance on top. + if (m_entriesInHistoryFile >= m_maxHistoryEntries + maxOverflowEntries) + rewriteHistoryFile(); + } + + void handleLine(const char* line) + { + Lexer lex(line); + if (lex.atEnd()) + return; // Blank line: not a command, not an error. + + // History replay ("!!" / "!") is expanded before command dispatch. + if (lex.consumeChar('!')) + return replayHistory(line, lex); + + std::string_view word = lex.nextToken(); + // `p` takes an lldb-style format suffix, as in "p/x", so the command and + // its format arrive as one token. + std::string_view command = word; + std::string_view format; + if (size_t slash = word.find('/'); slash != std::string_view::npos) { + command = word.substr(0, slash); + format = word.substr(slash + 1); + } + + auto is = [&](const char* name) { + return word == name; + }; + + if (is("help")) { + handleHelp(lex); + return; + } + if (is("q") || is("quit") || is("exit")) { + m_isQuitting = confirmQuit(); + return; + } + if (is("st") || is("status")) { + recordCommand(line); + printStatus(); + return; + } + if (is("hi") || is("hist") || is("history")) { + // A bare `history` only lists the history. Recording it would make + // the last entry of every listing be the command that asked for it. + if (!lex.atEnd()) + recordCommand(line); + handleHistory(lex); + return; + } + if (is("detach")) { + recordCommand(line); + detach(); + return; + } + if (is("attach")) { + recordCommand(line); + // Optional "--pid"/"-p" before the number: "attach --pid 42" == "attach 42". + if (!lex.consumeToken("--pid")) + lex.consumeToken("-p"); + pid_t pid = -1; + if (!lex.consumePID(pid) || !lex.atEnd()) { + fputs("Usage: attach [--pid|-p] \n", stderr); + return; + } + attach(pid); + return; + } + if (is("sn") || is("snap") || is("snapshot")) { + recordCommand(line); + handleSnapshot(lex); + return; + } + if (is("th") || is("thread")) { + recordCommand(line); + handleThread(lex); + return; + } + if (command == "p" || command == "print") { + recordCommand(line); + handlePrint(format, lex); + return; + } + + recordCommand(line); + fprintf(stderr, "mya: Unknown command '%.*s'\n", static_cast(word.length()), word.data()); + } + + void runInteractive() + { +#if HAVE(READLINE) + for (;;) { + char* line = readline(prompt); + if (!line) { + putchar('\n'); + break; + } + handleLine(line); + free(line); + if (m_isQuitting) + break; + } +#else + char line[4096]; + fputs(prompt, stdout); + fflush(stdout); + while (fgets(line, sizeof(line), stdin)) { + line[strcspn(line, "\n")] = '\0'; + handleLine(line); + if (m_isQuitting) + break; + fputs(prompt, stdout); + fflush(stdout); + } + putchar('\n'); +#endif + cleanup(); // About to quit. + } + + std::vector m_history; + unsigned m_maxHistoryEntries { defaultMaxHistoryEntries }; + unsigned m_entriesInHistoryFile { 0 }; // Actual number of commands in the file (may exceed target capacity). + FILE* m_historyFile { nullptr }; + int m_historyDirDescriptor { -1 }; + HashMap> m_processes; + // m_snapshotsById owns the Snapshots; m_snapshots only records capture order. + HashMap> m_snapshotsById; + DoublyLinkedList m_snapshots; + RefPtr m_currentProcess; + unsigned m_currentSnapshot { 0 }; // Snapshot id in use; 0 means none. + bool m_isQuitting { false }; +}; + +} // namespace Mya + +WTF_ALLOW_UNSAFE_BUFFER_USAGE_END + +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + +int main(int argc, char** argv) +{ +#if (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) + return Mya::Shell().run(argc, argv); +#else + UNUSED_PARAM(argc); + UNUSED_PARAM(argv); + printf("Not supported platform for mya\n"); + return 1; +#endif // (OS(MACOS) || USE(APPLE_INTERNAL_SDK)) && !PLATFORM(MACCATALYST) && !PLATFORM(IOS_FAMILY_SIMULATOR) +} diff --git a/Source/JavaScriptCore/shell/CMakeLists.txt b/Source/JavaScriptCore/shell/CMakeLists.txt index 368d4380a7ba..520103a3a721 100644 --- a/Source/JavaScriptCore/shell/CMakeLists.txt +++ b/Source/JavaScriptCore/shell/CMakeLists.txt @@ -22,8 +22,30 @@ if (ENABLE_FUZZILLI) list(APPEND jsc_SOURCES ../fuzzilli/Fuzzilli.cpp) endif () +# mya analyzes process corpses through Mach task APIs, so it only builds on +# Apple platforms. Its sources do not compile elsewhere. +if (APPLE) + set(mya_SOURCES ../mya/mya.cpp) + set(mya_FRAMEWORKS + JavaScriptCore + WTF + bmalloc + ) + + set(mya_PRIVATE_INCLUDE_DIRECTORIES + $ + ) + + # The corpse support mya uses lives in the tools static library. + set(mya_LIBRARIES JavaScriptCoreTools edit) +endif () + WEBKIT_EXECUTABLE_DECLARE(jsc) +if (APPLE) + WEBKIT_EXECUTABLE_DECLARE(mya) +endif () + if (DEVELOPER_MODE) set(testapi_SOURCES ../API/tests/CompareAndSwapTest.cpp @@ -82,6 +104,29 @@ if (DEVELOPER_MODE) set(testdfg_PRIVATE_INCLUDE_DIRECTORIES ${jsc_PRIVATE_INCLUDE_DIRECTORIES}) set(testdfg_FRAMEWORKS ${jsc_FRAMEWORKS}) + # The tools library and its tests are only relevant for Apple platforms. + if (APPLE) + set(testLibJSCTools_SOURCES + ../corpse/tests/CorpseAddressTest.cpp + ../corpse/tests/CorpseByteParserTest.cpp + ../corpse/tests/CorpseExportsTrieTest.cpp + ../corpse/tests/CorpseProcessTest.cpp + ../corpse/tests/CorpseRegionTest.cpp + ../corpse/tests/CorpseSnapshotTest.cpp + ../corpse/tests/CorpseSymbolTest.cpp + ../corpse/tests/CorpseThreadTest.cpp + ../corpse/tests/LibJSCToolsTestUtilities.cpp + ../corpse/tests/testLibJSCTools.cpp + ) + set(testLibJSCTools_DEFINITIONS ${jsc_PRIVATE_DEFINITIONS}) + set(testLibJSCTools_PRIVATE_INCLUDE_DIRECTORIES + ${jsc_PRIVATE_INCLUDE_DIRECTORIES} + ${JAVASCRIPTCORE_DIR}/corpse + ) + set(testLibJSCTools_FRAMEWORKS ${jsc_FRAMEWORKS}) + set(testLibJSCTools_LIBRARIES JavaScriptCoreTools) + endif () + set(testwasmdebugger_SOURCES ../wasm/debugger/testwasmdebugger.cpp @@ -114,6 +159,10 @@ if (DEVELOPER_MODE) WEBKIT_EXECUTABLE_DECLARE(testdfg) WEBKIT_EXECUTABLE_DECLARE(testwasmdebugger) + if (APPLE) + WEBKIT_EXECUTABLE_DECLARE(testLibJSCTools) + endif () + if (COMPILER_IS_GCC_OR_CLANG) WEBKIT_ADD_TARGET_CXX_FLAGS(testb3 -Wno-array-bounds) WEBKIT_ADD_TARGET_CXX_FLAGS(testair -Wno-array-bounds) @@ -129,6 +178,14 @@ if (SHOULD_INSTALL_JS_SHELL) install(TARGETS jsc DESTINATION "${LIBEXEC_INSTALL_DIR}") endif () +if (APPLE) + WEBKIT_EXECUTABLE(mya) + + if (SHOULD_INSTALL_JS_SHELL) + install(TARGETS mya DESTINATION "${LIBEXEC_INSTALL_DIR}") + endif () +endif () + if (DEVELOPER_MODE) WEBKIT_EXECUTABLE(testapi) WEBKIT_EXECUTABLE(testRegExp) @@ -138,6 +195,10 @@ if (DEVELOPER_MODE) WEBKIT_EXECUTABLE(testdfg) WEBKIT_EXECUTABLE(testwasmdebugger) + if (APPLE) + WEBKIT_EXECUTABLE(testLibJSCTools) + endif () + WEBKIT_ADD_PREFIX_HEADER(testapi ../JavaScriptCorePrefix.h PREFIX_NO_CODEGEN PREFIX_LANGUAGES CXX) WEBKIT_REUSE_PREFIX_HEADER(testb3 testapi ../JavaScriptCorePrefix.h PREFIX_LANGUAGES CXX) WEBKIT_REUSE_PREFIX_HEADER(testwasmdebugger testapi ../JavaScriptCorePrefix.h PREFIX_LANGUAGES CXX) diff --git a/Source/JavaScriptCore/shell/PlatformCocoa.cmake b/Source/JavaScriptCore/shell/PlatformCocoa.cmake index 06765386e803..3ebba0330703 100644 --- a/Source/JavaScriptCore/shell/PlatformCocoa.cmake +++ b/Source/JavaScriptCore/shell/PlatformCocoa.cmake @@ -14,6 +14,7 @@ set_source_files_properties(${testapi_OBJC_SOURCES} PROPERTIES ) WEBKIT_GENERATE_ENTITLEMENTS(jsc USING ../Scripts/process-entitlements.sh) +WEBKIT_GENERATE_ENTITLEMENTS(mya USING ../Scripts/process-entitlements.sh) if (DEVELOPER_MODE) WEBKIT_GENERATE_ENTITLEMENTS(testapi USING ../Scripts/process-entitlements.sh) WEBKIT_GENERATE_ENTITLEMENTS(testRegExp USING ../Scripts/process-entitlements.sh) @@ -21,5 +22,6 @@ if (DEVELOPER_MODE) WEBKIT_GENERATE_ENTITLEMENTS(testb3 USING ../Scripts/process-entitlements.sh) WEBKIT_GENERATE_ENTITLEMENTS(testair USING ../Scripts/process-entitlements.sh) WEBKIT_GENERATE_ENTITLEMENTS(testdfg USING ../Scripts/process-entitlements.sh) + WEBKIT_GENERATE_ENTITLEMENTS(testLibJSCTools USING ../Scripts/process-entitlements.sh) endif () diff --git a/Tools/CISupport/ews-build/steps.py b/Tools/CISupport/ews-build/steps.py index 747e08a9f677..ac6dac226db9 100644 --- a/Tools/CISupport/ews-build/steps.py +++ b/Tools/CISupport/ews-build/steps.py @@ -3586,6 +3586,8 @@ def runCommand(self, command): self.binaryFailures.append('testdfg') if jsc_results.get('allApiTestsPassed') is False: self.binaryFailures.append('testapi') + if jsc_results.get('allLibJSCToolsTestsPassed') is False: + self.binaryFailures.append('testLibJSCTools') self.flaky = jsc_results.get('flakyAndPassed') if self.flaky: self.setProperty(self.prefix + 'flaky_and_passed', self.flaky) diff --git a/Tools/Scripts/run-javascriptcore-tests b/Tools/Scripts/run-javascriptcore-tests index ca65f7ce5536..05f94394c5d1 100755 --- a/Tools/Scripts/run-javascriptcore-tests +++ b/Tools/Scripts/run-javascriptcore-tests @@ -100,6 +100,7 @@ my $runTestB3 = RUN_IF_NO_TESTS_SPECIFIED; my $runTestDFG = RUN_IF_NO_TESTS_SPECIFIED; my $runTestAPI = RUN_IF_NO_TESTS_SPECIFIED; my $runTestWasmDebugger = RUN_IF_NO_TESTS_SPECIFIED; +my $runTestLibJSCTools = RUN_IF_NO_TESTS_SPECIFIED; my $runJSCStress = RUN_IF_NO_TESTS_SPECIFIED; my $runMozillaTests = RUN_IF_NO_TESTS_SPECIFIED; @@ -207,6 +208,17 @@ if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTWASMDEBUGGER}) { } } +if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTLIBJSCTOOLS}) { + if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTLIBJSCTOOLS} eq "true") { + $runTestLibJSCTools = ENV_VAR_SAYS_DO_RUN; + } elsif ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTLIBJSCTOOLS} eq "false") { + $runTestLibJSCTools = ENV_VAR_SAYS_DONT_RUN; + } else { + print "Don't recognize value for RUN_JAVASCRIPTCORE_TESTS_TESTLIBJSCTOOLS environment variable: '" + . $ENV{RUN_JAVASCRIPTCORE_TESTS_TESTLIBJSCTOOLS} . "'. Should be set to 'true' or 'false'.\n"; + } +} + if ($ENV{RUN_JAVASCRIPTCORE_TESTS_BUILD}) { if ($ENV{RUN_JAVASCRIPTCORE_TESTS_BUILD} eq "true") { $buildJSC = 1; @@ -255,6 +267,7 @@ my $testb3Default = defaultStringForTestState($runTestB3); my $testDFGDefault = defaultStringForTestState($runTestDFG); my $testapiDefault = defaultStringForTestState($runTestAPI); my $testWasmDebuggerDefault = defaultStringForTestState($runTestWasmDebugger); +my $testLibJSCToolsDefault = defaultStringForTestState($runTestLibJSCTools); my $jscStressDefault = defaultStringForTestState($runJSCStress); my $mozillaTestsDefault = defaultStringForTestState($runMozillaTests); my $jitStressTestsDefault = $runJITStressTests ? "will run" : " will not run"; @@ -280,6 +293,7 @@ Usage: $programName [options] [options to pass to build system] --[no-]testdfg Only run (or don't run) testdfg (default: $testDFGDefault) --[no-]testapi Only run (or don't run) testapi (default: $testapiDefault) --[no-]testwasmdebugger Only run (or don't run) testwasmdebugger (default: $testWasmDebuggerDefault) + --[no-]testlibjsctools Only run (or don't run) testLibJSCTools (default: $testLibJSCToolsDefault) --[no-]jsc-stress Only run (or don't run) the JSC stress tests (default: $jscStressDefault) --[no-]mozilla-tests Only run (or don't run) the Mozilla tests (default: $mozillaTestsDefault) --[no-]jit-stress-tests Run (or don't run) the JIT stress tests (default: $jitStressTestsDefault) @@ -347,6 +361,7 @@ Environment Variables: - set RUN_JAVASCRIPTCORE_TESTS_TESTDFG to "true" or "false" (no quotes) to determine if we run testdfg by default. - set RUN_JAVASCRIPTCORE_TESTS_TESTAPI to "true" or "false" (no quotes) to determine if we run testapi by default. - set RUN_JAVASCRIPTCORE_TESTS_TESTWASMDEBUGGER to "true" or "false" (no quotes) to determine if we run testwasmdebugger by default. + - set RUN_JAVASCRIPTCORE_TESTS_TESTLIBJSCTOOLS to "true" or "false" (no quotes) to determine if we run testLibJSCTools by default. - set RUN_JAVASCRIPTCORE_TESTS_BUILD to "true" or "false" (no quotes) to set the should-we-build-before-running-tests setting. - set RUN_JAVASCRIPTCORE_TESTS_EXTRA_TESTS to the path of a yaml file or a directory of JS files to be run as part of run-javascriptcore-tests. @@ -368,6 +383,7 @@ GetOptions( 'testdfg!' => \$runTestDFG, 'testapi!' => \$runTestAPI, 'testwasmdebugger!' => \$runTestWasmDebugger, + 'testlibjsctools!' => \$runTestLibJSCTools, 'jsc-stress!' => \$runJSCStress, 'jitless-wasm!' => \$runJITlessWasm, 'mozilla-tests!' => \$runMozillaTests, @@ -423,6 +439,7 @@ if ($runTestMasm == DO_RUN || $runTestDFG == DO_RUN || $runTestAPI == DO_RUN || $runTestWasmDebugger == DO_RUN + || $runTestLibJSCTools == DO_RUN || $runJSCStress == DO_RUN || $runMozillaTests == DO_RUN) { $specificTestsSpecified = 1; @@ -549,9 +566,16 @@ $runTestB3 = enableTestOrNot($runTestB3); $runTestDFG = enableTestOrNot($runTestDFG); $runTestAPI = enableTestOrNot($runTestAPI); $runTestWasmDebugger = enableTestOrNot($runTestWasmDebugger); +$runTestLibJSCTools = enableTestOrNot($runTestLibJSCTools); $runJSCStress = enableTestOrNot($runJSCStress); $runMozillaTests = enableTestOrNot($runMozillaTests); +# libJavaScriptCoreTools and its test is built on Mach APIs and only available on Apple platforms. +if ($runTestLibJSCTools && $^O ne "darwin") { + print "Not running testLibJSCTools: it is only available on Apple platforms.\n"; + $runTestLibJSCTools = DONT_RUN; +} + my @buildArgs; if ($buildJSC) { @@ -815,6 +839,7 @@ if ($runTestAPI) { runTest("testapi", "allApiTestsPassedWithSystemMalloc", "Malloc=1"); } if ($runTestWasmDebugger) { runTest("testwasmdebugger", "allWasmDebuggerTestsPassed") } +if ($runTestLibJSCTools) { runTest("testLibJSCTools", "allLibJSCToolsTestsPassed") } # Find JavaScriptCore directory chdirWebKit(); diff --git a/Tools/Scripts/webkitperl/BuildSubproject.pm b/Tools/Scripts/webkitperl/BuildSubproject.pm index 61575d672e99..6e6a22565777 100644 --- a/Tools/Scripts/webkitperl/BuildSubproject.pm +++ b/Tools/Scripts/webkitperl/BuildSubproject.pm @@ -171,7 +171,9 @@ if (isCMakeBuild()) { unless (isAnyWindows()) { # By default we build using all of the available CPUs $makeArgs .= ($makeArgs ? " " : "") . "-j" . numberOfCPUs() if $makeArgs !~ /-j\s*\d+/; - $buildTarget = "jsc testb3 testair testapi testmasm testdfg testwasmdebugger $makeArgs"; + $buildTarget = "jsc testb3 testair testapi testmasm testdfg testwasmdebugger"; + $buildTarget .= " testLibJSCTools" if $^O eq "darwin"; # libJavaScriptCoreTools only available on Apple platforms. + $buildTarget .= " $makeArgs"; } elsif (canUseNinja()) { $buildTarget .= "jsc testapi testmasm"; } diff --git a/Tools/Scripts/webkitpy/common/config/ports.py b/Tools/Scripts/webkitpy/common/config/ports.py index e0076f90c565..2f1f6d218bcd 100644 --- a/Tools/Scripts/webkitpy/common/config/ports.py +++ b/Tools/Scripts/webkitpy/common/config/ports.py @@ -219,6 +219,7 @@ def run_javascriptcore_tests_command(self, build_style=None): command.append("--no-testdfg") command.append("--no-testapi") command.append("--no-testwasmdebugger") + command.append("--no-testlibjsctools") if 'JSCTESTS_OPTIONS' in os.environ: command += os.environ['JSCTESTS_OPTIONS'].split() return self._append_build_style_flag(command, build_style) diff --git a/Tools/Scripts/webkitpy/common/config/ports_unittest.py b/Tools/Scripts/webkitpy/common/config/ports_unittest.py index 05aa71aa459c..b3cb23bd688a 100644 --- a/Tools/Scripts/webkitpy/common/config/ports_unittest.py +++ b/Tools/Scripts/webkitpy/common/config/ports_unittest.py @@ -67,4 +67,4 @@ def test_jsconly_port(self): self.assertEqual(JscOnlyPort().build_jsc_command(), DeprecatedPort().script_shell_command("build-jsc") + ["--jsc-only"]) self.assertEqual(JscOnlyPort().build_jsc_command(build_style="release"), DeprecatedPort().script_shell_command("build-jsc") + ["--jsc-only", "--release"]) self.assertEqual(JscOnlyPort().build_jsc_command(build_style="debug"), DeprecatedPort().script_shell_command("build-jsc") + ["--jsc-only", "--debug"]) - self.assertEqual(JscOnlyPort().run_javascriptcore_tests_command(build_style="debug"), DeprecatedPort().script_shell_command("run-javascriptcore-tests") + ['--no-fail-fast', '--no-testmasm', '--no-testair', '--no-testb3', '--no-testdfg', '--no-testapi', '--no-testwasmdebugger', '--debug']) + self.assertEqual(JscOnlyPort().run_javascriptcore_tests_command(build_style="debug"), DeprecatedPort().script_shell_command("run-javascriptcore-tests") + ['--no-fail-fast', '--no-testmasm', '--no-testair', '--no-testb3', '--no-testdfg', '--no-testapi', '--no-testwasmdebugger', '--no-testlibjsctools', '--debug']) From f54d0579631ecf491974f33c46fcae9e47cee8bc Mon Sep 17 00:00:00 2001 From: Antoine Quint Date: Fri, 28 Aug 2026 01:06:22 -0700 Subject: [PATCH 018/103] [scroll-animations] keep track of style scope for `animation-timeline` and `{scroll|view}-timeline-name` properties https://bugs.webkit.org/show_bug.cgi?id=322739 rdar://186008235 Reviewed by Anne van Kesteren. Track the style scope when parsing the `animation-timeline`, `scroll-timeline-name` and `view-timeline-name` properties such that in a future patch we may correctly look up progress-based timelines across style scopes and address bug 322013. We simply change `Style::CustomIdent` to `Style::ScopedName` in `Style::SingleAnimationTimeline` and `Style::ProgressTimelineName` and additionally switch from `AtomString` to `Style::ScopedName` to represent a progress-based timeline's name. * Source/WebCore/animation/CSSAnimation.cpp: (WebCore::CSSAnimation::syncStyleOriginatedTimeline): * Source/WebCore/animation/ScrollTimeline.cpp: (WebCore::ScrollTimeline::create): (WebCore::ScrollTimeline::createInactiveStyleOriginatedTimeline): (WebCore::ScrollTimeline::ScrollTimeline): (WebCore::ScrollTimeline::matchesAnonymousScrollFunctionForSource const): * Source/WebCore/animation/ScrollTimeline.h: (WebCore::ScrollTimeline::setName): * Source/WebCore/animation/StyleOriginatedTimelinesController.cpp: (WebCore::StyleOriginatedTimelinesController::registerNamedScrollTimeline): (WebCore::StyleOriginatedTimelinesController::updateCSSAnimationsAssociatedWithNamedTimeline): (WebCore::StyleOriginatedTimelinesController::registerNamedViewTimeline): (WebCore::StyleOriginatedTimelinesController::attachAnimation): (WebCore::StyleOriginatedTimelinesController::updateNamedTimelineMapForTimelineScope): * Source/WebCore/animation/StyleOriginatedTimelinesController.h: * Source/WebCore/animation/ViewTimeline.cpp: (WebCore::ViewTimeline::create): (WebCore::ViewTimeline::ViewTimeline): (WebCore::ViewTimeline::matchesAnonymousViewFunctionForSubject const): * Source/WebCore/animation/ViewTimeline.h: * Source/WebCore/style/Styleable.cpp: (WebCore::Styleable::updateCSSScrollTimelines const): (WebCore::Styleable::updateCSSViewTimelines const): * Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.cpp: (WebCore::Style::CSSValueConversion::operator): * Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.h: (WebCore::Style::SingleAnimationTimeline::SingleAnimationTimeline): (WebCore::Style::SingleAnimationTimeline::isScopedName const): (WebCore::Style::SingleAnimationTimeline::tryScopedName const): (WebCore::Style::SingleAnimationTimeline::isCustomIdent const): Deleted. (WebCore::Style::SingleAnimationTimeline::tryCustomIdent const): Deleted. * Source/WebCore/style/values/scroll-animations/StyleProgressTimelineName.h: Canonical link: https://commits.webkit.org/320026@main --- Source/WebCore/animation/CSSAnimation.cpp | 6 +-- Source/WebCore/animation/ScrollTimeline.cpp | 8 ++-- Source/WebCore/animation/ScrollTimeline.h | 11 ++--- .../StyleOriginatedTimelinesController.cpp | 42 +++++++++---------- .../StyleOriginatedTimelinesController.h | 5 ++- Source/WebCore/animation/ViewTimeline.cpp | 12 +++--- Source/WebCore/animation/ViewTimeline.h | 5 ++- Source/WebCore/style/Styleable.cpp | 24 +++++------ .../StyleSingleAnimationTimeline.cpp | 2 +- .../animations/StyleSingleAnimationTimeline.h | 12 +++--- .../StyleProgressTimelineName.h | 4 +- 11 files changed, 67 insertions(+), 64 deletions(-) diff --git a/Source/WebCore/animation/CSSAnimation.cpp b/Source/WebCore/animation/CSSAnimation.cpp index d7d3bf1bc97c..6b51c4b75a8f 100644 --- a/Source/WebCore/animation/CSSAnimation.cpp +++ b/Source/WebCore/animation/CSSAnimation.cpp @@ -217,7 +217,7 @@ void CSSAnimation::syncStyleOriginatedTimeline() [&](const CSS::Keyword::None&) { setTimeline(nullptr); }, - [&](const Style::CustomIdent&) { + [&](const Style::ScopedName&) { CheckedRef styleOriginatedTimelinesController = document->ensureStyleOriginatedTimelinesController(); styleOriginatedTimelinesController->attachAnimation(*this); }, @@ -235,7 +235,7 @@ void CSSAnimation::syncStyleOriginatedTimeline() if (existingViewTimeline->matchesAnonymousViewFunctionForSubject(viewFunction, m_backingStyleZoomForLength, *owningElement())) return; } - auto viewTimeline = ViewTimeline::create(nullAtom(), viewFunction->axis, viewFunction->insets, m_backingStyleZoomForLength); + auto viewTimeline = ViewTimeline::create({ nullAtom() }, viewFunction->axis, viewFunction->insets, m_backingStyleZoomForLength); viewTimeline->setSubject(*owningElement()); setTimeline(WTF::move(viewTimeline)); } @@ -243,7 +243,7 @@ void CSSAnimation::syncStyleOriginatedTimeline() // If we're not dealing with a named timeline, we should make sure we have no // pending attachment operation for this timeline name. - if (!m_backingStyleAnimation.timeline().isCustomIdent()) { + if (!m_backingStyleAnimation.timeline().isScopedName()) { CheckedRef styleOriginatedTimelinesController = document->ensureStyleOriginatedTimelinesController(); styleOriginatedTimelinesController->removePendingOperationsForCSSAnimation(*this); } diff --git a/Source/WebCore/animation/ScrollTimeline.cpp b/Source/WebCore/animation/ScrollTimeline.cpp index 2b4d8c4acf68..8d7585301a30 100644 --- a/Source/WebCore/animation/ScrollTimeline.cpp +++ b/Source/WebCore/animation/ScrollTimeline.cpp @@ -83,7 +83,7 @@ Ref ScrollTimeline::create(Document& document, ScrollTimelineOpt return timeline; } -Ref ScrollTimeline::create(const AtomString& name, ScrollAxis axis) +Ref ScrollTimeline::create(const Style::ScopedName& name, ScrollAxis axis) { return adoptRef(*new ScrollTimeline(name, axis)); } @@ -95,7 +95,7 @@ Ref ScrollTimeline::create(Scroller scroller, ScrollAxis axis) Ref ScrollTimeline::createInactiveStyleOriginatedTimeline(const AtomString& name) { - auto timeline = adoptRef(*new ScrollTimeline(name, ScrollAxis::Block)); + Ref timeline = adoptRef(*new ScrollTimeline({ name }, ScrollAxis::Block)); timeline->m_isInactiveStyleOriginatedTimeline = true; return timeline; } @@ -110,7 +110,7 @@ ScrollTimeline::ScrollTimeline() { } -ScrollTimeline::ScrollTimeline(const AtomString& name, ScrollAxis axis) +ScrollTimeline::ScrollTimeline(const Style::ScopedName& name, ScrollAxis axis) : ScrollTimeline() { m_axis = axis; @@ -416,7 +416,7 @@ void ScrollTimeline::animationTimingDidChange(WebAnimation& animation) bool ScrollTimeline::matchesAnonymousScrollFunctionForSource(const Style::ScrollFunction& scrollFunction, const Styleable& source) const { - return m_isStyleOriginated && m_name.isEmpty() && m_scroller == scrollFunction->scroller && m_axis == scrollFunction->axis && m_source.styleable() == source; + return m_isStyleOriginated && m_name.name.isEmpty() && m_scroller == scrollFunction->scroller && m_axis == scrollFunction->axis && m_source.styleable() == source; } #if ENABLE(THREADED_ANIMATIONS) diff --git a/Source/WebCore/animation/ScrollTimeline.h b/Source/WebCore/animation/ScrollTimeline.h index 0d8121f782dd..3066644e2fe1 100644 --- a/Source/WebCore/animation/ScrollTimeline.h +++ b/Source/WebCore/animation/ScrollTimeline.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ class ComputedStyle; class ScrollTimeline : public AnimationTimeline { public: static Ref create(Document&, ScrollTimelineOptions&& = { }); - static Ref create(const AtomString&, ScrollAxis); + static Ref create(const Style::ScopedName&, ScrollAxis); static Ref create(Scroller, ScrollAxis); static Ref createInactiveStyleOriginatedTimeline(const AtomString& name); @@ -64,8 +65,8 @@ class ScrollTimeline : public AnimationTimeline { ScrollAxis axis() const { return m_axis; } void setAxis(ScrollAxis axis) { m_axis = axis; } - const AtomString& name() const LIFETIME_BOUND { return m_name; } - void setName(const AtomString& name) { m_name = name; } + const Style::ScopedName& name() const LIFETIME_BOUND { return m_name; } + void setName(const Style::ScopedName& name) { m_name = name; } bool isInactiveStyleOriginatedTimeline() const { return m_isInactiveStyleOriginatedTimeline; } @@ -98,7 +99,7 @@ class ScrollTimeline : public AnimationTimeline { #endif protected: - explicit ScrollTimeline(const AtomString&, ScrollAxis); + explicit ScrollTimeline(const Style::ScopedName&, ScrollAxis); struct Data { float scrollOffset { 0 }; @@ -140,7 +141,7 @@ class ScrollTimeline : public AnimationTimeline { WeakStyleable m_source; ScrollAxis m_axis { ScrollAxis::Block }; - AtomString m_name; + Style::ScopedName m_name; Scroller m_scroller { Scroller::Self }; WeakPtr m_timelineScopeElement; CurrentTimeData m_cachedCurrentTimeData { }; diff --git a/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp b/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp index 7a1f8d93e69d..4ec6b9052f3d 100644 --- a/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp +++ b/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp @@ -183,11 +183,11 @@ void StyleOriginatedTimelinesController::updateTimelineForTimelineScope(const Re } } -void StyleOriginatedTimelinesController::registerNamedScrollTimeline(const AtomString& name, const Styleable& source, ScrollAxis axis) +void StyleOriginatedTimelinesController::registerNamedScrollTimeline(const Style::ScopedName& scopedName, const Styleable& source, ScrollAxis axis) { - LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::registerNamedScrollTimeline: " << name << " source: " << source); + LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::registerNamedScrollTimeline: " << scopedName.name << " source: " << source); - auto& timelines = timelinesForName(name); + auto& timelines = timelinesForName(scopedName.name); auto existingTimelineIndex = timelines.findIf([&](auto& timeline) { return !is(timeline) && timeline->sourceStyleable() == source; @@ -197,11 +197,11 @@ void StyleOriginatedTimelinesController::registerNamedScrollTimeline(const AtomS auto& existingScrollTimeline = timelines[existingTimelineIndex].get(); existingScrollTimeline.setAxis(axis); } else { - auto newScrollTimeline = ScrollTimeline::create(name, axis); + auto newScrollTimeline = ScrollTimeline::create(scopedName, axis); newScrollTimeline->setSource(source); - updateTimelineForTimelineScope(newScrollTimeline, name); + updateTimelineForTimelineScope(newScrollTimeline, scopedName.name); timelines.append(WTF::move(newScrollTimeline)); - updateCSSAnimationsAssociatedWithNamedTimeline(name); + updateCSSAnimationsAssociatedWithNamedTimeline(scopedName.name); } } @@ -216,8 +216,8 @@ void StyleOriginatedTimelinesController::updateCSSAnimationsAssociatedWithNamedT if (RefPtr cssAnimation = dynamicDowncast(animation.get())) { if (!cssAnimation->owningElement()) continue; - if (auto timelineName = cssAnimation->backingStyleAnimation().timeline().tryCustomIdent()) { - if (timelineName->value == name) + if (auto timelineName = cssAnimation->backingStyleAnimation().timeline().tryScopedName()) { + if (timelineName->name == name) cssAnimationsWithMatchingTimelineName.add(*cssAnimation); } } @@ -255,11 +255,11 @@ void StyleOriginatedTimelinesController::documentDidResolveStyle() m_removedTimelines.clear(); } -void StyleOriginatedTimelinesController::registerNamedViewTimeline(const AtomString& name, const Styleable& subject, ScrollAxis axis, const Style::ViewTimelineInsetItem& insets, const Style::ZoomFactor& usedZoomForLength) +void StyleOriginatedTimelinesController::registerNamedViewTimeline(const Style::ScopedName& scopedName, const Styleable& subject, ScrollAxis axis, const Style::ViewTimelineInsetItem& insets, const Style::ZoomFactor& usedZoomForLength) { - LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::registerNamedViewTimeline: " << name << " subject: " << subject); + LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::registerNamedViewTimeline: " << scopedName.name << " subject: " << subject); - auto& timelines = timelinesForName(name); + auto& timelines = timelinesForName(scopedName.name); auto existingTimelineIndex = timelines.findIf([&](auto& timeline) { if (RefPtr viewTimeline = dynamicDowncast(timeline)) @@ -274,14 +274,14 @@ void StyleOriginatedTimelinesController::registerNamedViewTimeline(const AtomStr existingViewTimeline->setAxis(axis); existingViewTimeline->setInsets(ResolvableViewTimelineInsets { insets, usedZoomForLength }); } else { - auto newViewTimeline = ViewTimeline::create(name, axis, insets, usedZoomForLength); + auto newViewTimeline = ViewTimeline::create(scopedName, axis, insets, usedZoomForLength); newViewTimeline->setSubject(subject); - updateTimelineForTimelineScope(newViewTimeline, name); + updateTimelineForTimelineScope(newViewTimeline, scopedName.name); timelines.append(WTF::move(newViewTimeline)); } if (!hasExistingTimeline) - updateCSSAnimationsAssociatedWithNamedTimeline(name); + updateCSSAnimationsAssociatedWithNamedTimeline(scopedName.name); } void StyleOriginatedTimelinesController::unregisterNamedTimeline(const AtomString& name, const Styleable& styleable) @@ -336,14 +336,14 @@ void StyleOriginatedTimelinesController::attachAnimation(CSSAnimation& animation if (!target) return; - auto timelineName = protectedAnimation->backingStyleAnimation().timeline().tryCustomIdent(); + auto timelineName = protectedAnimation->backingStyleAnimation().timeline().tryScopedName(); if (!timelineName) return; - LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::attachAnimation: " << timelineName->value << " target: " << *target); + LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::attachAnimation: " << timelineName->name << " target: " << *target); auto relevantTimelineScopeElement = [&] -> RefPtr { - auto timelineScopeElements = relatedTimelineScopeElements(*timelineName); + auto timelineScopeElements = relatedTimelineScopeElements(Style::CustomIdent { timelineName->name }); if (timelineScopeElements.isEmpty()) return nullptr; // Find the nearest parent within timelineScopeElements. @@ -356,7 +356,7 @@ void StyleOriginatedTimelinesController::attachAnimation(CSSAnimation& animation return nullptr; }(); - auto it = m_nameToTimelineMap.find(timelineName->value); + auto it = m_nameToTimelineMap.find(timelineName->name); auto hasNamedTimeline = it != m_nameToTimelineMap.end() && it->value.containsIf([&](auto& timeline) { auto timelineScope = timeline->timelineScopeDeclaredElement(); if (timelineScope && timelineScope.get() != relevantTimelineScopeElement.get()) @@ -380,13 +380,13 @@ void StyleOriginatedTimelinesController::attachAnimation(CSSAnimation& animation // scroll timeline, or, // 2. the name is not within scope and the timeline is null. if (relevantTimelineScopeElement) - protectedAnimation->setTimeline(&inactiveNamedTimeline(timelineName->value)); + protectedAnimation->setTimeline(&inactiveNamedTimeline(timelineName->name)); else protectedAnimation->setTimeline(nullptr); } else { auto& timelines = it->value; RefPtr timeline = determineTimelineForElement(timelines, *target, relevantTimelineScopeElement.get()); - LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::attachAnimation: " << timelineName->value << " styleable: " << *target << " attaching to timeline of element: " << originatingElement(*timeline)); + LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::attachAnimation: " << timelineName->name << " styleable: " << *target << " attaching to timeline of element: " << originatingElement(*timeline)); // A deferred inactive timeline means there was a conflict with multiple timelines existing within // a parent element with a "timeline-scope" property. In that case, we must reconsider timeline attachment // once style resolution completes as further updates may occur that would yield a different timeline @@ -441,7 +441,7 @@ void StyleOriginatedTimelinesController::updateNamedTimelineMapForTimelineScope( // We need to unregister all timelines that are no longer within this scope. for (auto& timeline : namedTimelinesToUnregister) { if (auto associatedElement = originatingElement(timeline).styleable()) - unregisterNamedTimeline(timeline->name(), *associatedElement); + unregisterNamedTimeline(timeline->name().name, *associatedElement); } break; } diff --git a/Source/WebCore/animation/StyleOriginatedTimelinesController.h b/Source/WebCore/animation/StyleOriginatedTimelinesController.h index 15d347e02d64..5a9fe0f8419d 100644 --- a/Source/WebCore/animation/StyleOriginatedTimelinesController.h +++ b/Source/WebCore/animation/StyleOriginatedTimelinesController.h @@ -26,6 +26,7 @@ #pragma once #include "CSSAnimation.h" +#include "ScopedName.h" #include "ScrollAxis.h" #include "StyleNameScope.h" #include "Styleable.h" @@ -64,8 +65,8 @@ class StyleOriginatedTimelinesController final : public CanMakeCheckedPtr> ViewTimeline::create(Document& document, ViewTime if (!insets) return Exception { ExceptionCode::TypeError }; - auto viewTimeline = ViewTimeline::create(nullAtom(), options.axis, WTF::move(*insets), Style::ZoomFactor::none()); + auto viewTimeline = ViewTimeline::create({ nullAtom() }, options.axis, WTF::move(*insets), Style::ZoomFactor::none()); viewTimeline->setSubject(options.subject.get()); if (auto subject = options.subject) @@ -68,13 +68,13 @@ ExceptionOr> ViewTimeline::create(Document& document, ViewTime return viewTimeline; } -Ref ViewTimeline::create(const AtomString& name, ScrollAxis axis, const Style::ViewTimelineInsetItem& insets, const Style::ZoomFactor& usedZoomForLength) +Ref ViewTimeline::create(const Style::ScopedName& scopedName, ScrollAxis axis, const Style::ViewTimelineInsetItem& insets, const Style::ZoomFactor& usedZoomForLength) { - return adoptRef(*new ViewTimeline(name, axis, insets, usedZoomForLength)); + return adoptRef(*new ViewTimeline(scopedName, axis, insets, usedZoomForLength)); } -ViewTimeline::ViewTimeline(const AtomString& name, ScrollAxis axis, const Style::ViewTimelineInsetItem& insets, const Style::ZoomFactor& usedZoomForLength) - : ScrollTimeline(name, axis) +ViewTimeline::ViewTimeline(const Style::ScopedName& scopedName, ScrollAxis axis, const Style::ViewTimelineInsetItem& insets, const Style::ZoomFactor& usedZoomForLength) + : ScrollTimeline(scopedName, axis) , m_insets({ .insets = insets, .zoom = usedZoomForLength }) { } @@ -552,7 +552,7 @@ Ref ViewTimeline::endOffset() const bool ViewTimeline::matchesAnonymousViewFunctionForSubject(const Style::ViewFunction& viewFunction, const Style::ZoomFactor& usedZoomForLength, const Styleable& subject) const { return isStyleOriginated() - && name().isEmpty() + && name().name.isEmpty() && m_insets.insets == viewFunction->insets && m_insets.zoom == usedZoomForLength && axis() == viewFunction->axis diff --git a/Source/WebCore/animation/ViewTimeline.h b/Source/WebCore/animation/ViewTimeline.h index d250e6dbe5e8..ea1b2c8f8c54 100644 --- a/Source/WebCore/animation/ViewTimeline.h +++ b/Source/WebCore/animation/ViewTimeline.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +73,7 @@ struct StickinessAdjustmentData { class ViewTimeline final : public ScrollTimeline { public: static ExceptionOr> create(Document&, ViewTimelineOptions&&); - static Ref create(const AtomString&, ScrollAxis, const Style::ViewTimelineInsetItem&, const Style::ZoomFactor&); + static Ref create(const Style::ScopedName&, ScrollAxis, const Style::ViewTimelineInsetItem&, const Style::ZoomFactor&); const Element* NODELETE subject() const; const WeakStyleable subjectStyleable() const { return m_subject; } @@ -102,7 +103,7 @@ class ViewTimeline final : public ScrollTimeline { WebAnimationTime NODELETE epsilon() const; private: - ViewTimeline(const AtomString&, ScrollAxis, const Style::ViewTimelineInsetItem&, const Style::ZoomFactor&); + ViewTimeline(const Style::ScopedName&, ScrollAxis, const Style::ViewTimelineInsetItem&, const Style::ZoomFactor&); ScrollTimeline::Data computeTimelineData(UseCachedCurrentTime = UseCachedCurrentTime::Yes) const final; std::pair intervalForTimelineRangeName(const ScrollTimeline::Data&, Style::SingleAnimationRangeName) const; diff --git a/Source/WebCore/style/Styleable.cpp b/Source/WebCore/style/Styleable.cpp index 5d414c2c2fa4..3438baf7bdba 100644 --- a/Source/WebCore/style/Styleable.cpp +++ b/Source/WebCore/style/Styleable.cpp @@ -874,9 +874,9 @@ void Styleable::updateCSSScrollTimelines(const Style::ComputedStyle* currentStyl [](CSS::Keyword::None) { // Nothing to register. }, - [&](const Style::CustomIdent& identifier) { - styleOriginatedTimelinesController->registerNamedScrollTimeline(identifier.value, *this, scrollTimeline.axis()); - registeredScrollTimelineNames.add(identifier.value); + [&](const Style::ScopedName& scopedName) { + styleOriginatedTimelinesController->registerNamedScrollTimeline(scopedName, *this, scrollTimeline.axis()); + registeredScrollTimelineNames.add(scopedName.name); } ); } @@ -889,9 +889,9 @@ void Styleable::updateCSSScrollTimelines(const Style::ComputedStyle* currentStyl [](CSS::Keyword::None) { // Nothing to unregister. }, - [&](const Style::CustomIdent& identifier) { - if (!registeredScrollTimelineNames.contains(identifier.value)) - styleOriginatedTimelinesController->unregisterNamedTimeline(identifier.value, *this); + [&](const Style::ScopedName& scopedName) { + if (!registeredScrollTimelineNames.contains(scopedName.name)) + styleOriginatedTimelinesController->unregisterNamedTimeline(scopedName.name, *this); } ); } @@ -911,9 +911,9 @@ void Styleable::updateCSSViewTimelines(const Style::ComputedStyle* currentStyle, [](CSS::Keyword::None) { // Nothing to register. }, - [&](const Style::CustomIdent& identifier) { - styleOriginatedTimelinesController->registerNamedViewTimeline(identifier.value, *this, viewTimeline.axis(), viewTimeline.inset(), afterChangeStyle.usedZoomForLength()); - registeredViewTimelineNames.add(identifier.value); + [&](const Style::ScopedName& scopedName) { + styleOriginatedTimelinesController->registerNamedViewTimeline(scopedName, *this, viewTimeline.axis(), viewTimeline.inset(), afterChangeStyle.usedZoomForLength()); + registeredViewTimelineNames.add(scopedName.name); } ); } @@ -926,9 +926,9 @@ void Styleable::updateCSSViewTimelines(const Style::ComputedStyle* currentStyle, [](CSS::Keyword::None) { // Nothing to unregister. }, - [&](const Style::CustomIdent& identifier) { - if (!registeredViewTimelineNames.contains(identifier.value)) - styleOriginatedTimelinesController->unregisterNamedTimeline(identifier.value, *this); + [&](const Style::ScopedName& scopedName) { + if (!registeredViewTimelineNames.contains(scopedName.name)) + styleOriginatedTimelinesController->unregisterNamedTimeline(scopedName.name, *this); } ); } diff --git a/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.cpp b/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.cpp index cd9728ed64d5..8eb540c43f76 100644 --- a/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.cpp +++ b/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.cpp @@ -59,7 +59,7 @@ auto CSSValueConversion::operator()(BuilderState& state if (RefPtr viewValue = dynamicDowncast(value)) return toStyleFromCSSValue(state, *viewValue); - return toStyleFromCSSValue(state, value); + return toStyleFromCSSValue(state, value); } } // namespace Style diff --git a/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.h b/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.h index 4eaf3be9d4b1..7b9f53465f33 100644 --- a/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.h +++ b/Source/WebCore/style/values/animations/StyleSingleAnimationTimeline.h @@ -24,7 +24,7 @@ #pragma once -#include +#include #include #include #include @@ -45,8 +45,8 @@ struct SingleAnimationTimeline { { } - SingleAnimationTimeline(CustomIdent&& identifier) - : m_value { identifier } + SingleAnimationTimeline(ScopedName&& name) + : m_value { name } { } @@ -62,8 +62,8 @@ struct SingleAnimationTimeline { bool isAuto() const { return std::holds_alternative(m_value); } bool isNone() const { return std::holds_alternative(m_value); } - bool isCustomIdent() const { return std::holds_alternative(m_value); } - std::optional tryCustomIdent() const { return isCustomIdent() ? std::make_optional(std::get(m_value)) : std::nullopt; } + bool isScopedName() const { return std::holds_alternative(m_value); } + std::optional tryScopedName() const { return isScopedName() ? std::make_optional(std::get(m_value)) : std::nullopt; } bool isScrollFunction() const { return std::holds_alternative(m_value); } std::optional tryScrollFunction() const { return isScrollFunction() ? std::make_optional(std::get(m_value)) : std::nullopt; } bool isViewFunction() const { return std::holds_alternative(m_value); } @@ -77,7 +77,7 @@ struct SingleAnimationTimeline { bool operator==(const SingleAnimationTimeline&) const = default; private: - Variant m_value; + Variant m_value; }; // MARK: - Conversion diff --git a/Source/WebCore/style/values/scroll-animations/StyleProgressTimelineName.h b/Source/WebCore/style/values/scroll-animations/StyleProgressTimelineName.h index 9765852c063e..ab4ee3268013 100644 --- a/Source/WebCore/style/values/scroll-animations/StyleProgressTimelineName.h +++ b/Source/WebCore/style/values/scroll-animations/StyleProgressTimelineName.h @@ -24,7 +24,7 @@ #pragma once -#include +#include #include namespace WebCore { @@ -32,7 +32,7 @@ namespace Style { // = none | // https://drafts.csswg.org/scroll-animations/#propdef-scroll-timeline-name -struct ProgressTimelineName : ValueOrKeyword { +struct ProgressTimelineName : ValueOrKeyword { using Base::Base; bool isNone() const { return isKeyword(); } From 5f47279d0d16d660d14660d902f81cf04877eca5 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 01:11:06 -0700 Subject: [PATCH 019/103] [JSC][Wasm] Inline BBQ array.new_default for v128 https://bugs.webkit.org/show_bug.cgi?id=322526 Reviewed by Yusuke Suzuki. BBQ array.new_default of v128 still called operationWasmArrayNewEmpty. Pin a zero vector and use the existing store loop. * Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp: Canonical link: https://commits.webkit.org/320027@main --- Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp | 24 ++++++--------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp b/Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp index 9fa3ffba4628..75c02372110f 100644 --- a/Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp +++ b/Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp @@ -1611,22 +1611,6 @@ void BBQJIT::emitAllocateGCArrayUninitialized(GPRReg resultGPR, TypeSignatureInd [[nodiscard]] PartialResult BBQJIT::addArrayNewDefault(TypeSignatureIndex typeIndex, ExpressionType size, ExpressionType& result) { StorageType elementType = getArrayElementType(typeIndex); - // FIXME: We don't have a good way to fill V128s yet so just make a call. - if (elementType.unpacked().isV128()) { - Vector arguments = { - instanceValue(), - Value::fromI32(typeIndex.rawIndex()), - size, - }; - result = topValue(TypeKind::Arrayref); - emitCCall(operationWasmArrayNewEmpty, arguments, result); - - Location resultLocation = loadIfNecessary(result); - emitThrowOnNullReference(ExceptionType::BadArrayNew, resultLocation); - - LOG_INSTRUCTION("ArrayNewDefault", typeIndex, size, RESULT(result)); - return { }; - } GPRReg resultGPR; { @@ -1639,7 +1623,13 @@ void BBQJIT::emitAllocateGCArrayUninitialized(GPRReg resultGPR, TypeSignatureInd JIT_COMMENT(m_jit, "Array allocation done do initialization"); std::optional> sizeScratch; Location sizeLocation = materializeToGPR(size, sizeScratch); - Value initValue = Value::fromI64(Wasm::isRefType(elementType.unpacked()) ? JSValue::encode(jsNull()) : 0); + Value initValue; + if (elementType.unpacked().isV128()) { + // FIXME: We should have V128 Constant. + materializeVectorConstant(v128_t { }, Location::fromFPR(wasmScratchFPR)); + initValue = Value::pinned(TypeKind::V128, Location::fromFPR(wasmScratchFPR)); + } else + initValue = Value::fromI64(Wasm::isRefType(elementType.unpacked()) ? JSValue::encode(jsNull()) : 0); emitArrayGetPayload(elementType, resultGPR, scratchGPR); From ff8fd01e634aff2e9a52c7b8a280f4f74cf60df4 Mon Sep 17 00:00:00 2001 From: Diego Pino Garcia Date: Fri, 28 Aug 2026 01:20:21 -0700 Subject: [PATCH 020/103] [GLIB] imported/w3c/web-platform-tests/css/css-values/ch-unit-017.html is failing https://bugs.webkit.org/show_bug.cgi?id=206001 Reviewed by Carlos Garcia Campos. The test fails because two divs of expected same size are actually different. One of the divs uses 'writing-mode: vertical-rl' with a custom 'OpenType @font-face' and 'width: 5ch'. This information is relevant because GLIB ports always build with OPENTYPE_VERTICAL enabled. A Font's constructor calls 'platformGlyphInit', which computes the font's zero-glyph width (used to resolve the CSS 'ch' unit) via 'FontInlines::widthForGlyph'. That method decides between the glyph's horizontal and vertical advance based on whether 'm_verticalData' is set, but at that point in construction it isn't set yet (it's only initialized further down in the constructor), so the width is always computed as horizontal, even for a genuinely vertical font. Simply moving initialization of 'm_verticalData' earlier doesn't work, as it creates side effects in a few tests that are legitimately passing (e.g fast/writing-mode/text-orientation-basic.html). Instead, this change leaves 'm_verticalData' initialization where it is and performs an independent 'OpenTypeVerticalData' lookup inside 'platformGlyphInit', with the goal of computing 'zero-glyph''s width correctly. As a result of this change, a few 'ch-units-vrl-XXX' tests which were wrongly passing before are now failing. * LayoutTests/platform/glib/TestExpectations: * Source/WebCore/platform/graphics/Font.cpp: (WebCore::m_shouldNotBeUsedForArabic): (WebCore::Font::platformGlyphInit): Canonical link: https://commits.webkit.org/320028@main --- LayoutTests/platform/glib/TestExpectations | 13 +++++-------- Source/WebCore/platform/graphics/Font.cpp | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/LayoutTests/platform/glib/TestExpectations b/LayoutTests/platform/glib/TestExpectations index c8cde2869f6f..227a66f04cea 100644 --- a/LayoutTests/platform/glib/TestExpectations +++ b/LayoutTests/platform/glib/TestExpectations @@ -438,8 +438,6 @@ imported/w3c/web-platform-tests/css/css-values/ch-unit-012.html [ Pass ] # Test passing since added in r258661. imported/w3c/web-platform-tests/css/css-writing-modes/baseline-with-orthogonal-flow-001.html [ Pass ] -imported/w3c/web-platform-tests/css/css-writing-modes/ch-units-vrl-007.html [ Pass ] -imported/w3c/web-platform-tests/css/css-writing-modes/ch-units-vrl-008.html [ Pass ] imported/w3c/web-platform-tests/css/css-writing-modes/mongolian-orientation-001.html [ Pass ] imported/w3c/web-platform-tests/css/css-writing-modes/mongolian-orientation-002.html [ Pass ] @@ -3161,10 +3159,12 @@ imported/w3c/web-platform-tests/html/semantics/forms/the-meter-element/meter-app imported/w3c/web-platform-tests/html/semantics/forms/the-meter-element/meter-appearance-none-suboptimum-value-rendering.html [ ImageOnlyFailure ] # Passes for GTK/WPE (but not for Mac/iOS) after WPT update of css-writing-modes tests +webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-001.html [ Pass ] webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-004.html [ Pass ] webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-006.html [ Pass ] webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-008.html [ Pass ] webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-009.html [ Pass ] +webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-012.html [ Pass ] webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-015.html [ Pass ] webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-016.html [ Pass ] webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-018.html [ Pass ] @@ -3175,6 +3175,8 @@ webkit.org/b/214291 imported/w3c/web-platform-tests/css/css-writing-modes/availa webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/background-position-vrl-018.xht [ ImageOnlyFailure ] webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/background-position-vrl-020.xht [ ImageOnlyFailure ] webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/background-position-vrl-022.xht [ ImageOnlyFailure ] +webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/ch-units-vrl-003.html [ ImageOnlyFailure ] +webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/ch-units-vrl-004.html [ ImageOnlyFailure ] webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/normal-flow-overconstrained-vrl-002.xht [ ImageOnlyFailure ] webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/normal-flow-overconstrained-vrl-004.xht [ ImageOnlyFailure ] webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/overconstrained-rel-pos-ltr-top-bottom-vrl-002.xht [ ImageOnlyFailure ] @@ -3219,9 +3221,6 @@ imported/w3c/web-platform-tests/css/css-images/infinite-radial-gradient-refcrash webkit.org/b/203448 imported/w3c/web-platform-tests/css/css-position/position-absolute-dynamic-static-position-table-cell.html [ Pass ] -webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-001.html [ Pass ] -webkit.org/b/209080 imported/w3c/web-platform-tests/css/css-writing-modes/available-size-012.html [ Pass ] - webkit.org/b/215799 imported/w3c/web-platform-tests/css/css-content/quotes-005.html [ ImageOnlyFailure ] # WIRELESS_PLAYBACK_TARGET not enabled. @@ -3775,6 +3774,7 @@ webkit.org/b/264574 imported/w3c/web-platform-tests/css/css-pseudo/backdrop-anim webkit.org/b/264575 imported/w3c/web-platform-tests/css/css-text-decor/text-combine-emphasis.html [ ImageOnlyFailure ] webkit.org/b/264577 imported/w3c/web-platform-tests/css/css-text/hyphens/hyphens-auto-004.html [ ImageOnlyFailure ] +webkit.org/b/264577 imported/w3c/web-platform-tests/css/css-text/hyphens/hyphens-vertical-004.html [ ImageOnlyFailure ] webkit.org/b/264577 imported/w3c/web-platform-tests/css/css-text/line-breaking/line-breaking-replaced-002.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-bottom-left-radius-001.html [ ImageOnlyFailure ] @@ -4065,9 +4065,6 @@ webkit.org/b/201981 http/wpt/resource-timing/rt-resources-per-worker.html [ Fail webkit.org/b/306019 imported/w3c/web-platform-tests/resource-timing/resource_timing.worker.html [ Failure Pass ] -# ch units should be ignored in these tests. -webkit.org/b/206001 imported/w3c/web-platform-tests/css/css-values/ch-unit-017.html [ ImageOnlyFailure ] - # WPT fetch tests. webkit.org/b/206416 imported/w3c/web-platform-tests/fetch/range/sw.https.window.html [ Failure ] diff --git a/Source/WebCore/platform/graphics/Font.cpp b/Source/WebCore/platform/graphics/Font.cpp index 0fb72460c425..79cff4688b98 100644 --- a/Source/WebCore/platform/graphics/Font.cpp +++ b/Source/WebCore/platform/graphics/Font.cpp @@ -109,7 +109,7 @@ Font::Font(const FontPlatformData& platformData, Origin origin, IsInterstitial i platformGlyphInit(); platformCharWidthInit(); #if ENABLE(OPENTYPE_VERTICAL) - if (platformData.orientation() == FontOrientation::Vertical && orientationFallback == IsOrientationFallback::No) { + if (platformData.orientation() == FontOrientation::Vertical && !isTextOrientationFallback()) { m_verticalData = FontCache::forCurrentThread().verticalData(platformData); m_hasVerticalGlyphs = m_verticalData.get() && m_verticalData->hasVerticalMetrics(); } @@ -193,8 +193,17 @@ void Font::platformGlyphInit() Glyph zeroGlyph = { 0 }; if (RefPtr page = glyphPage(GlyphPage::pageNumberForCodePoint('0'))) zeroGlyph = page->glyphDataForCharacter('0').glyph; - if (zeroGlyph) - m_fontMetrics.setZeroWidth(widthForGlyph(zeroGlyph)); + if (zeroGlyph) { +#if ENABLE(OPENTYPE_VERTICAL) + RefPtr verticalData; + if (platformData().orientation() == FontOrientation::Vertical && !isTextOrientationFallback()) + verticalData = FontCache::forCurrentThread().verticalData(platformData()); + if (verticalData) + m_fontMetrics.setZeroWidth(verticalData->advanceHeight(this, zeroGlyph)); + else +#endif + m_fontMetrics.setZeroWidth(widthForGlyph(zeroGlyph)); + } // Use the width of the CJK water ideogram (U+6C34) as the // approximated width of ideograms in the font, as mentioned in From 5c1c95db5e0ea336071ae634567cda9aaa80c181 Mon Sep 17 00:00:00 2001 From: Nikolas Zimmermann Date: Fri, 28 Aug 2026 01:38:07 -0700 Subject: [PATCH 021/103] [webkit-sysprof] Add a frame cycle breakdown to analyze https://bugs.webkit.org/show_bug.cgi?id=322589 Reviewed by Carlos Garcia Campos. webkit-sysprof analyze reports 'theoretical FPS', the number of frames the engine produced over the analyzed duration. That says nothing about how long one frame took, nor where its time went. Add a 'frame cycle' reconstruction: a cycle runs from one LayerTreeHostRenderingUpdate of a process to the next, so the frame rate is 1 / cycle duration, and it splits into the rendering update, waiting for compositing, compositing and idle. The report gives the phase medians, how much of the analyzed duration the cycles cover, and the implied FPS while rendering, so a rate falling short can be told from a phase. Also add --explain, off by default, which prints what the report does and does not measure, and reject a timespan of no length rather than reporting zeroes for it. * Tools/Scripts/webkit-sysprof/README.md: * Tools/Scripts/webkit-sysprof/pyproject.toml: * Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py: (_add_subcommand): (build_parser): (main): * Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/__init__.py: (_frame_rendering_reason): (_counted): (analyze): (_calculate_statistics): (_prepare_report): (_prepare_rendering_report): (_covered_duration): (_cycle_in_vblank_intervals): (_prepare_frame_cycle_report): (_calculate_frame_compositions_per_vblank): (_calculate_vblanks_per_rendering_update): (_render_text_report): (_analyzed_duration_sec): (_theoretical_fps_explanation): (_refresh_rate_explanation): (_percentile_strings): (_analyzed_cycles): (_render_frame_cycle_numbers): (_statistics_to_strings): (format_statistic): (_sysprof_data_to_high_level_representation): Deleted. (_statistics_to_strings.format_statistic): Deleted. * Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/explanations.py: Added. * Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py: Added. (_MarkIndex): (_MarkIndex.__init__): (_MarkIndex.spans_within): (_MarkIndex.last_end_of_marks_beginning_within): (_marks_beginning_within): (within): (_compositing_indices_by_process): (_compositing_spans): (_own_compositing_end): (calculate_frame_cycles): (_cycles_of_one_process): * Tools/Scripts/webkit-sysprof/webkitsysprof/dump/__init__.py: (dump): (_marks_to_rows): * Tools/Scripts/webkit-sysprof/webkitsysprof/histogram/__init__.py: (delta_histogram): (_delta_times_ms): (_calculate_optimal_bins): (_plot_delta_time_distribution): (_calculate_delta_times_ms): Deleted. * Tools/Scripts/webkit-sysprof/webkitsysprof/parser/direct_parser.py: * Tools/Scripts/webkit-sysprof/webkitsysprof/summary/__init__.py: (_print_document_summary): * Tools/Scripts/webkit-sysprof/webkitsysprof/tests/frame_cycles_unittest.py: Added. (frame_cycle_report): (FrameCyclesTest): (FrameCyclesTest.test_phases_partition_every_resolved_cycle): (FrameCyclesTest.test_composition_ending_after_the_cycle_is_reported_as_an_overrun): (FrameCyclesTest.test_a_cycle_after_an_overrunning_composition_still_resolves): (FrameCyclesTest.test_cycles_without_compositing_marks_are_counted_but_not_analyzed): (FrameCyclesTest.test_the_gap_between_two_rendering_periods_is_reported_as_a_long_cycle): (FrameCyclesTest.test_the_compositing_mark_name_is_resolved_per_cycle): (FrameCyclesTest.test_compositing_is_over_when_the_last_compositing_mark_is): (FrameCyclesTest.test_compositing_overlapping_the_rendering_update_still_counts): (FrameCyclesTest.test_a_cycle_reaching_past_the_timespan_is_left_out_of_it): (FrameCyclesTest.test_a_capture_without_compositing_marks_keeps_its_cycles): (FrameCyclesTest.test_coverage_is_the_share_of_the_window_the_analyzed_cycles_took): (FrameCyclesTest.test_a_single_cycle_reports_no_percentiles_rather_than_failing): (FrameCyclesTest.test_no_cycles_at_all): (FrameCyclesTest.test_cycles_per_vblank_interval_is_unknown_without_vblank_marks): (FrameCyclesTest.test_cycles_are_restricted_to_the_ones_lying_within_the_timespan): (FrameCyclesTest.test_compositing_running_inside_the_rendering_update_still_resolves): (FrameCyclesTest.test_a_cycle_never_runs_between_two_processes): (FrameCyclesTest.test_a_cycle_spent_entirely_on_an_earlier_composition_is_analyzed): (FrameCyclesTest.test_coverage_merges_the_cycles_of_two_processes): (FrameCyclesTest.test_a_composition_begun_before_the_window_is_still_seen_in_it): (FrameCyclesTest.test_a_stall_is_seen_past_a_shorter_mark_of_the_same_name): (FrameCyclesTest.test_a_capture_of_negative_length_reports_no_rate): (FrameCyclesTest.test_no_cycles_says_so_rather_than_blaming_a_zero_median): (FrameCyclesTest.test_every_overrunning_composition_of_a_stall_is_counted): (FrameCyclesTest.test_a_cycle_running_a_sliver_of_an_inherited_composition_is_measured): (FrameCyclesTest.test_a_cycle_running_only_an_inherited_composition_is_measured): (FrameCyclesTest.test_a_cycle_composited_beside_its_update_only_is_measured_as_zero): (FrameCyclesTest.test_a_mark_ending_before_the_capture_keeps_its_own_end): (FrameCyclesTest.test_a_gap_between_two_compositions_is_a_wait_not_compositing): (FrameCyclesTest.test_two_renderers_of_one_kind_are_told_apart_by_their_process): (FrameCyclesTest.test_two_updates_at_one_instant_belong_to_two_processes): (FrameCyclesTest.test_a_report_without_a_rate_prints_no_rate): (FrameCyclesTest.test_a_steady_slow_capture_keeps_its_cycles): (FrameCyclesTest.test_the_composition_overrun_median_describes_the_overrunning_cycles): (FrameCyclesTest.test_an_unknown_refresh_rate_is_not_reported_as_zero): (FrameCyclesTest.test_a_zero_median_vblank_interval_is_not_reported_as_shared_timestamps): * Tools/Scripts/webkit-sysprof/webkitsysprof/tests/helpers.py: Added. (mark): (sysprof_data): (approx): (approx.__init__): (approx.__eq__): (approx.__repr__): (SysprofTestCase): (SysprofTestCase.setUp): (SysprofTestCase.stdout): * Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py: (SubcommandsTest): (SubcommandsTest.test_summary): (SubcommandsTest.test_dump_marks_csv): (SubcommandsTest.test_dump_counters_csv): (SubcommandsTest.test_dump_marks_json): (SubcommandsTest.test_dump_counters_json): (SubcommandsTest.test_analyze_text): (SubcommandsTest.test_analyze_text_with_explanations): (SubcommandsTest.test_analyze_json_percentiles_stay_within_the_data_range): (SubcommandsTest.test_analyze_json): (SubcommandsTest.test_analyze_json_frame_cycle_phases_add_up_to_cycle_duration): (SubcommandsTest.test_analyze_json_statistics_cover_all_relevant_marks): (SubcommandsTest.test_analyze_with_a_timespan_holding_vblanks_but_no_rendering_update): (SubcommandsTest.test_analyze_json_leaves_out_a_cycle_reaching_past_the_timespan): (SubcommandsTest.test_analyze_resolves_a_cycle_whose_compositing_marks_trimming_drops): (SubcommandsTest.test_explain_is_rejected_for_the_json_format_by_the_module_api): (SubcommandsTest.test_analyze_rejects_a_timespan_it_cannot_honour): (SubcommandsTest.test_analyze_clamps_a_timespan_reaching_past_the_capture): (SubcommandsTest.test_vblanks_per_rendering_update_does_not_depend_on_the_mark_order): (SubcommandsTest.test_a_capture_of_no_length_reports_no_rate): (SubcommandsTest.test_delta_histogram_deltas_come_from_the_requested_mark): (SubcommandsTest.test_delta_histogram_honours_the_timespan): (SubcommandsTest.test_analyze_json_counts_every_vblank_interval_of_the_timespan): (SubcommandsTest.test_frame_compositions_outside_the_vblank_range_are_left_out): (SubcommandsTest.test_frame_rendering_reasons_bucket_a_frame_that_named_none): (SubcommandsTest.test_frame_rendering_reasons_bucket_a_frame_that_named_none.did_render_frame): (SubcommandsTest.test_explain_reaches_the_report_through_the_command_line): (SubcommandsTest.test_a_capture_of_its_own_broken_timespan_is_no_usage_error): (SubcommandsTest.test_statistics_are_matched_by_wording_rather_than_word_position): (SubcommandsTest.test_explaining_an_empty_frame_cycle_section_still_explains_it): (SubcommandsTest.test_a_broken_capture_is_no_usage_error_even_with_a_timespan): (SubcommandsTest.test_a_window_meeting_the_capture_at_one_point_is_rejected): (SubcommandsTest.test_json_percentiles_keep_the_fiftieth): (SubcommandsTest.test_delta_histogram_keeps_the_processes_apart): (SubcommandsTest.test_composition_overrun_says_nothing_where_nothing_was_analyzed): (SubcommandsTest.test_an_update_ending_on_the_first_refresh_spans_it): (SubcommandsTest.test_refreshes_that_composited_nothing_are_samples_of_nothing): (SubcommandsTest.test_dump_csv_carries_every_column_of_a_row): (SubcommandsTest.test_a_window_without_refreshes_keeps_the_capture_interval): (SubcommandsTest.test_analyze_with_custom_timespan): (SubcommandsTest.test_analyze_with_custom_timespan_begin): (SubcommandsTest.test_analyze_with_custom_timespan_end): (_capture_stdout): Deleted. * Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py: Added. (UtilsTest): (UtilsTest.test_spans_are_merged_where_they_overlap_or_touch): (UtilsTest.test_a_timespan_bound_is_digits_and_nothing_else): (UtilsTest.test_percentiles_outside_the_range_of_the_quantiles_are_rejected): (UtilsTest.test_a_bare_bound_is_the_begin_of_the_timespan): (UtilsTest.test_vblank_intervals_do_not_depend_on_the_order_of_the_marks): (UtilsTest.test_reshaping_the_same_parsed_data_twice_yields_the_same_result): * Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py: (msec_to_nsec): (UsageError): (mark_begin): (parse_timespan_argument): (parse_timespan_argument.parse_bound): (sysprof_data_with_marks_by_name): (check_timespan_holds_data): (trim_marks_by_name_to_timespan): (mark_in_timespan): (merged_spans): (sample_statistics): (percentiles): (mark_pid): (marks_by_process): (marks_in_time_order): (intervals_between_marks): (display_refreshes): (median_vblank_interval): (trim_sysprof_data_to_timespan): Deleted. Canonical link: https://commits.webkit.org/320029@main --- Tools/Scripts/webkit-sysprof/README.md | 61 +- Tools/Scripts/webkit-sysprof/pyproject.toml | 2 +- .../webkit-sysprof/webkitsysprof/__main__.py | 53 +- .../webkitsysprof/analyze/__init__.py | 593 ++++++++++----- .../webkitsysprof/analyze/explanations.py | 110 +++ .../webkitsysprof/cycles/__init__.py | 258 +++++++ .../webkitsysprof/dump/__init__.py | 6 +- .../webkitsysprof/histogram/__init__.py | 60 +- .../webkitsysprof/parser/direct_parser.py | 5 + .../webkitsysprof/summary/__init__.py | 4 +- .../tests/frame_cycles_unittest.py | 709 ++++++++++++++++++ .../webkitsysprof/tests/helpers.py | 76 ++ .../tests/subcommands_unittest.py | 606 ++++++++++++++- .../webkitsysprof/tests/utils_unittest.py | 92 +++ .../webkitsysprof/utils/__init__.py | 276 ++++++- 15 files changed, 2615 insertions(+), 296 deletions(-) create mode 100644 Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/explanations.py create mode 100644 Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py create mode 100644 Tools/Scripts/webkit-sysprof/webkitsysprof/tests/frame_cycles_unittest.py create mode 100644 Tools/Scripts/webkit-sysprof/webkitsysprof/tests/helpers.py create mode 100644 Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py diff --git a/Tools/Scripts/webkit-sysprof/README.md b/Tools/Scripts/webkit-sysprof/README.md index 80c5bce2675b..1fdb0df7302f 100644 --- a/Tools/Scripts/webkit-sysprof/README.md +++ b/Tools/Scripts/webkit-sysprof/README.md @@ -25,11 +25,13 @@ webkit-sysprof dump [--marks|--counters] [-f csv|json] CAPTURE_FILE ``` `--marks` is the default when neither flag is given. `-f`/`--format`: `csv` (default) or -`json` — `json` prints the same rows as `csv`, just as a JSON array of objects. +`json`. `json` prints the same rows as `csv`, just as a JSON array of objects. `group` +names the kind of process a mark came from, e.g. `WebKit (Web)`, and `pid` the process +itself, which two of one kind share a group name for. ``` $ webkit-sysprof dump capture.syscap -group;name;message;time;duration;end_time +group;pid;name;message;time;duration;end_time ... $ webkit-sysprof dump --counters capture.syscap @@ -37,7 +39,7 @@ category;name;description;time;offset;value ... $ webkit-sysprof dump -f json capture.syscap -[{"group": "...", "name": "...", "message": "...", "time": ..., "duration": ..., "end_time": ...}, ...] +[{"group": "...", "pid": ..., "name": "...", "message": "...", "time": ..., "duration": ..., "end_time": ...}, ...] ``` ### `summary` @@ -68,17 +70,21 @@ Counters: 53 ### `analyze` Compute rendering statistics (vblank intervals, theoretical FPS, frame compositions per -vblank) and duration/count statistics for a fixed set of WebKit-specific marks (styling, -layout, rendering, compositing and tiles). +vblank), a frame cycle breakdown, and duration/count statistics for a fixed set of +marks (styling, layout, rendering, compositing and tiles). ``` -webkit-sysprof analyze CAPTURE_FILE [-f text|json] [-t TIMESPAN] +webkit-sysprof analyze CAPTURE_FILE [-f text|json] [-t TIMESPAN] [-e] ``` - `-f`/`--format`: `text` (default) or `json`. - `-t`/`--timespan`: restrict the analysis to a window in milliseconds relative to the - capture start, e.g. `0-5000`. Either side may be omitted (`500-` means "from 500ms to - the end", `-500` means "from the start to 500ms"). Defaults to the whole capture (`-`). + capture start, e.g. `0-5000`. Either side may be omitted (`500-` or `500` mean "from + 500ms to the end", `-500` means "from the start to 500ms"). Defaults to the whole + capture (`-`), and is clamped to it. +- `-e`/`--explain`: interleave the text report with paragraphs explaining how to read + it, in particular what theoretical FPS and the frame cycle phases do and do not + measure. ``` $ webkit-sysprof analyze capture.syscap @@ -90,27 +96,50 @@ Rendering: ... $ webkit-sysprof analyze -f json -t 0-2000 capture.syscap -{"document": {...}, "statistics": {...}, "rendering": {...}} +{"document": {...}, "statistics": {...}, "rendering": {...}, "frame_cycle": {...}} ``` Restricting the timespan matters more than it looks: page load dominates the tail of most metrics, so a whole-capture run largely measures startup. Skipping the first seconds is usually what you want when comparing configurations. +#### Theoretical FPS + +`frames rendered` counts `DidRenderFrame` marks of every process (one per composition, +emitted once it is painted and before the frame is handed over) and +`theoretical FPS` divides that by the analyzed duration. `analyze --explain` prints +what that does and does not say, and how it relates to the display refresh rate. + +Note that `vblanks per LayerTreeHostRenderingUpdate` (and its `more than 1 per update` +count) only covers the `LayerTreeHostRenderingUpdate` mark. A rendering update that fits +inside one vblank interval can still miss frames, because compositing happens after it. +Use the frame cycle for the complete per-frame budget. + +#### Frame cycle + +A frame cycle is one turn of the engine's rendering loop, split into the phases the +frame's time went into. `analyze --explain` prints what a cycle is, which mark +delimits each phase and how to read every number of the section. + #### Reading the mark statistics -Three marks are easy to misread: +Three marks measure something other than what their name suggests: -- `StyleRecalc` is an umbrella mark that nests `RenderTreeBuild`, - `PerformSubtreesLayout` and `CompositingUpdate`. Its duration is not style resolution - alone. Subtract the nested marks to get that. -- `CompositingUpdate` runs twice per rendering update, once inside `StyleRecalc` and - once after it, and the second one is much shorter. Its percentiles are therefore - bimodal rather than centered on a typical value. +- `StyleRecalc` is an umbrella mark: `RenderTreeBuild`, the `CompositingUpdate` that + follows a style change and, where style resolution interleaves layout, the layout + marks all run inside it, so its duration is not style resolution alone. +- `CompositingUpdate` is emitted wherever the compositing layers are updated, after a + style change, after layout and on scrolling, so its samples mix runs of very + different lengths and its percentiles are not centered on a typical value. - `PaintTile` runs on the painting threads, so summing it over a frame gives aggregate work rather than elapsed time. Its `#pixels` statistic is the dirty area taken from the mark's `dirty region x++` message. +Percentiles (P25, P75 and P99) are interpolated between the samples, so they lie +between the reported minimum and maximum, and are left out below two samples. +`analyze --explain` prints the long form of all of this in the report itself, next to +the table it applies to. + ### `delta-histogram` Plot a histogram of the time between consecutive occurrences of a given mark (e.g. to diff --git a/Tools/Scripts/webkit-sysprof/pyproject.toml b/Tools/Scripts/webkit-sysprof/pyproject.toml index db0306a6b062..ca27bbe94888 100644 --- a/Tools/Scripts/webkit-sysprof/pyproject.toml +++ b/Tools/Scripts/webkit-sysprof/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "webkitsysprof" version = "1.0.0" description = "WebKit-specific sysprof capture processing toolkit" -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = ["matplotlib"] [project.scripts] diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py index c09dbb92008c..a15a5193a845 100644 --- a/Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py @@ -2,19 +2,38 @@ import logging import os import sys -from typing import Optional, Sequence +from typing import Any, Callable, Optional, Sequence from .analyze import analyze from .dump import dump from .histogram import delta_histogram from .summary import summary +from .utils import UsageError + +TIMESPAN_HELP = ( + 'Window of the capture to read, as "-" in milliseconds from its' + ' start, e.g. 0-5000. Either side may be left out: "500-" and "500" both mean' + ' from 500ms to the end, "-500" the first 500ms. Anything else is an error.' +) + + +def _add_subcommand( + subparsers: Any, + name: str, + help_text: str, + func: Callable[[argparse.Namespace], None], +) -> argparse.ArgumentParser: + """Add a subcommand that knows its own parser, for reporting usage errors.""" + command_parser = subparsers.add_parser(name, help=help_text) + command_parser.set_defaults(func=func, command_parser=command_parser) + return command_parser def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="webkit-sysprof") subparsers = parser.add_subparsers(dest="command", required=True) - dump_parser = subparsers.add_parser("dump", help="Dump capture data as CSV") + dump_parser = _add_subcommand(subparsers, "dump", "Dump capture data as CSV", dump) dump_group = dump_parser.add_mutually_exclusive_group() dump_group.add_argument("--marks", action="store_true", help="Dump marks (default)") dump_group.add_argument("--counters", action="store_true", help="Dump counters") @@ -28,17 +47,15 @@ def build_parser() -> argparse.ArgumentParser: default="csv", help="Format to be printed to STDOUT", ) - dump_parser.set_defaults(func=dump) - summary_parser = subparsers.add_parser( - "summary", help="Print a summary of a capture" + summary_parser = _add_subcommand( + subparsers, "summary", "Print a summary of a capture", summary ) summary_parser.add_argument( "capture_file", metavar="CAPTURE_FILE", help="Path to a .capture file" ) - summary_parser.set_defaults(func=summary) - analyze_parser = subparsers.add_parser("analyze", help="Analyze capture") + analyze_parser = _add_subcommand(subparsers, "analyze", "Analyze capture", analyze) analyze_parser.add_argument( "capture_file", metavar="CAPTURE_FILE", help="Path to a .capture file" ) @@ -54,12 +71,21 @@ def build_parser() -> argparse.ArgumentParser: "--timespan", type=str, default="-", - help="Timespan in milliseconds e.g. 0-5000", + help=TIMESPAN_HELP, + ) + analyze_parser.add_argument( + "-e", + "--explain", + action="store_true", + help="Explain how to read the report, alongside the report itself" + " (text format only)", ) - analyze_parser.set_defaults(func=analyze) - histogram_parser = subparsers.add_parser( - "delta-histogram", help="Generate histogram from capture" + histogram_parser = _add_subcommand( + subparsers, + "delta-histogram", + "Generate histogram from capture", + delta_histogram, ) histogram_parser.add_argument( "capture_file", metavar="CAPTURE_FILE", help="Path to a .capture file" @@ -74,9 +100,8 @@ def build_parser() -> argparse.ArgumentParser: "--timespan", type=str, default="-", - help="Timespan in milliseconds e.g. 0-5000", + help=TIMESPAN_HELP, ) - histogram_parser.set_defaults(func=delta_histogram) return parser @@ -90,6 +115,8 @@ def main(argv: Optional[Sequence[str]] = None) -> None: args = parser.parse_args(argv) try: args.func(args) + except UsageError as error: + args.command_parser.error(str(error)) except BrokenPipeError: devnull = os.open(os.devnull, os.O_WRONLY) os.dup2(devnull, sys.stdout.fileno()) diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/__init__.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/__init__.py index 88e4a90e1eb0..3facf7b952aa 100644 --- a/Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/__init__.py +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/__init__.py @@ -1,22 +1,31 @@ import argparse +import bisect import logging import re -import statistics import collections import json from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union from ..parser import parse +from . import explanations +from ..cycles import PHASES, calculate_frame_cycles from ..utils import ( + UsageError, + check_timespan_holds_data, + mark_begin, + median_vblank_interval, + msec_to_sec, nsec_to_msec, nsec_to_sec, - msec_to_sec, parse_timespan_argument, - trim_sysprof_data_to_timespan, + merged_spans, + display_refreshes, + intervals_between_marks, + sample_statistics, + sysprof_data_with_marks_by_name, + trim_marks_by_name_to_timespan, ) -RELEVANT_PERCENTILES = [25, 50, 75, 99] - # Columns of the statistics tables, as (label, statistics key, percentile) triples. # The percentile is None for statistics that are not percentiles. The median is taken # from the "median" key rather than from the 50th percentile: both describe the same @@ -32,6 +41,15 @@ ("max", "max", None), ] +# Derived from the columns, so the percentiles computed and the ones printed cannot +# drift apart. The 50th is not a column - the median column prints the exact value - +# but stays for the JSON report, whose consumers already read it. +PERCENTILE_SHOWN_AS_MEDIAN = 50 +RELEVANT_PERCENTILES = sorted( + {percentile for _, _, percentile in STATISTICS_COLUMNS if percentile is not None} + | {PERCENTILE_SHOWN_AS_MEDIAN} +) + MARKS_RELEVANT_FOR_STATISTICS = [ "EventLoopRun", "RAFCallback", @@ -56,6 +74,22 @@ # PaintTile messages look like "Skia/CPU threaded, dirty region 768x512+256+56", # where the geometry is "x++". DIRTY_REGION_RE = re.compile(r"(\d+)x(\d+)\+(\d+)\+(\d+)") +REASONS_RE = re.compile(r"^reasons:([^|\n]*)") +TASKS_RE = re.compile(r"^tasks:\s*(\d+)") +SUBTREES_RE = re.compile(r"^subtrees:\s*(\d+)") +TILES_RE = re.compile(r"^dirty tiles:\s*(\d+)") + + +def _frame_rendering_reason(message: str) -> str: + """Why a frame was composited, as the DidRenderFrame message tells it.""" + match = REASONS_RE.match(message.strip()) + return (match.group(1).strip() if match is not None else "") or "_none" + + +def _counted(message: str, pattern: "re.Pattern[str]") -> Optional[int]: + """The number the pattern names in a mark message, None where it does not.""" + match = pattern.match(message.strip()) + return int(match.group(1)) if match is not None else None def _dirty_region_pixels(message: str) -> Optional[int]: @@ -71,15 +105,15 @@ def _dirty_region_pixels(message: str) -> Optional[int]: }, "EventLoopRun": lambda data: STATISTICAL_DATA_EXTRACTORS["_"](data) | { - "tasks": int(data["message"].split()[1]), + "tasks": _counted(data["message"], TASKS_RE), }, "PerformSubtreesLayout": lambda data: STATISTICAL_DATA_EXTRACTORS["_"](data) | { - "subtrees": int(data["message"].split()[1]) if data["message"] != "" else None, + "subtrees": _counted(data["message"], SUBTREES_RE), }, "UpdateTiles": lambda data: STATISTICAL_DATA_EXTRACTORS["_"](data) | { - "tiles": int(data["message"].split()[2]) if data["message"] != "" else None, + "tiles": _counted(data["message"], TILES_RE), }, "PaintTile": lambda data: STATISTICAL_DATA_EXTRACTORS["_"](data) | { @@ -89,105 +123,102 @@ def _dirty_region_pixels(message: str) -> Optional[int]: def analyze(args: argparse.Namespace) -> None: + if args.explain and args.format != "text": + raise UsageError("--explain only applies to the text format") timespan_begin, timespan_end = parse_timespan_argument(args.timespan) parsed_data = parse(args.capture_file, marks=True, counters=False) - trimmed_data = trim_sysprof_data_to_timespan( - parsed_data, timespan_begin, timespan_end - ) - data = _sysprof_data_to_high_level_representation(trimmed_data) + capture_begin, capture_end = parsed_data["document"]["timespan"] + check_timespan_holds_data(capture_begin, capture_end, timespan_begin, timespan_end) + untrimmed_data = sysprof_data_with_marks_by_name(parsed_data) + data = trim_marks_by_name_to_timespan(untrimmed_data, timespan_begin, timespan_end) logging.info("Preparing report...") - report = _prepare_report(data, timespan_begin, timespan_end) + # Frame cycles are built from the untrimmed capture and only then cut down to + # the timespan: trimming drops the marks that cross a timespan boundary, and a + # cycle at that boundary needs them. + report = _prepare_report(data, untrimmed_data) logging.info("Rendering report...") if args.format == "text": - _render_text_report(report) + _render_text_report(report, args.explain) elif args.format == "json": print(json.dumps(report)) else: raise NotImplementedError -def _sysprof_data_to_high_level_representation( - sysprof_data: Dict[str, Any], -) -> Dict[str, Any]: - high_level_representation: Dict[str, Any] = { - "document": sysprof_data["document"], - "all_marks": sysprof_data["marks"], - "marks": {}, - } - high_level_representation["document"]["timespan"] = { - "begin": sysprof_data["document"]["timespan"][0], - "end": sysprof_data["document"]["timespan"][1], - } - for mark in sysprof_data["marks"]: - high_level_representation["marks"].setdefault(mark["name"], []).append(mark) - for mark_name in high_level_representation["marks"]: - high_level_representation["marks"][mark_name].sort(key=lambda m: m["end_time"]) - return high_level_representation +def _calculate_statistics(values: Sequence[Union[int, float]]) -> Dict[str, Any]: + return sample_statistics(values, RELEVANT_PERCENTILES) def _prepare_report( sysprof_data: Dict[str, Any], - timespan_begin: Optional[int], - timespan_end: Optional[int], + untrimmed_sysprof_data: Dict[str, Any], ) -> Dict[str, Any]: - # TODO: Frame time. # TODO: Dropped frames. # TODO: Interaction latency. # TODO: System metrics. - report = { - "document": sysprof_data["document"], - "statistics": {}, - "rendering": _prepare_rendering_report(sysprof_data), + timespan_begin = sysprof_data["document"]["timespan"]["begin"] + timespan_end = sysprof_data["document"]["timespan"]["end"] + document = dict(sysprof_data["document"]) + document["timespan"] = { + "begin": nsec_to_msec(timespan_begin), + "end": nsec_to_msec(timespan_end), } - - statistics_input_data = _calculate_statistics_input_data( + mark_statistics = _calculate_statistics_input_data( MARKS_RELEVANT_FOR_STATISTICS, sysprof_data ) - statistics = statistics_input_data - for mark_name in statistics: - for input_name in statistics[mark_name]: - statistics[mark_name][input_name] = _calculate_statistics( - statistics[mark_name][input_name] + for mark_name in mark_statistics: + for input_name in mark_statistics[mark_name]: + mark_statistics[mark_name][input_name] = _calculate_statistics( + mark_statistics[mark_name][input_name] ) - report["statistics"].update(statistics) - report["document"]["timespan"]["begin"] = nsec_to_msec( - report["document"]["timespan"]["begin"] - ) - report["document"]["timespan"]["end"] = nsec_to_msec( - report["document"]["timespan"]["end"] - ) + report = { + "document": document, + "statistics": mark_statistics, + "rendering": _prepare_rendering_report( + sysprof_data, display_refreshes(sysprof_data) + ), + "frame_cycle": _prepare_frame_cycle_report( + untrimmed_sysprof_data, + timespan_begin, + timespan_end, + # From the whole capture, not from the window: a narrow window holds + # too few vblanks, and the cycles come from the whole capture too. + median_vblank_interval( + display_refreshes(untrimmed_sysprof_data) + ), + ), + } return report -def _prepare_rendering_report(sysprof_data: Dict[str, Any]) -> Dict[str, Any]: +def _prepare_rendering_report( + sysprof_data: Dict[str, Any], vblanks: Sequence[Dict[str, Any]] +) -> Dict[str, Any]: document_duration = nsec_to_sec( sysprof_data["document"]["timespan"]["end"] - sysprof_data["document"]["timespan"]["begin"] ) - vblanks = sysprof_data["marks"].get("DisplayLinkUpdate", []) - vblanks_per_rendering_update = _calculate_vblanks_per_rendering_update(sysprof_data) + intervals = intervals_between_marks(vblanks) + vblanks_per_rendering_update = _calculate_vblanks_per_rendering_update( + vblanks, sysprof_data["marks"].get("LayerTreeHostRenderingUpdate", []) + ) did_render_frames = sysprof_data["marks"].get("DidRenderFrame", []) report = { "vblanks": len(vblanks), - "vblank_interval_statistics": _calculate_statistics( - [ - nsec_to_msec(vblanks[i]["end_time"] - vblanks[i - 1]["end_time"]) - for i in range(1, len(vblanks)) - ] - ), + "vblank_interval_statistics": _calculate_statistics(intervals), "frames_rendered": len(did_render_frames), "theoretical_fps": ( - len(did_render_frames) / document_duration if document_duration != 0 else 0 + len(did_render_frames) / document_duration + if document_duration > 0 + else None ), - "frame_rendering_reasons": dict( - collections.Counter( - [mark["message"].split(":")[1].strip() for mark in did_render_frames] - ) + "frame_rendering_reasons": collections.Counter( + [_frame_rendering_reason(mark["message"]) for mark in did_render_frames] ), "frame_compositions_per_vblank_statistics": _calculate_statistics( - _calculate_frame_compositions_per_vblank(sysprof_data) + _calculate_frame_compositions_per_vblank(vblanks, did_render_frames) ), "vblanks_per_rendering_update": { "n_greater_than_1": len( @@ -199,62 +230,121 @@ def _prepare_rendering_report(sysprof_data: Dict[str, Any]) -> Dict[str, Any]: return report -def _calculate_frame_compositions_per_vblank(sysprof_data: Dict[str, Any]) -> List[int]: - vblanks = sysprof_data["marks"].get("DisplayLinkUpdate", []) - if len(vblanks) == 0: +def _covered_duration(cycles: Sequence[Dict[str, Any]]) -> float: + """How much of the timeline the cycles span, in milliseconds. + + Their spans are merged rather than summed, since the cycles of two processes + rendering at once overlap and would otherwise cover more time than there is. + """ + return nsec_to_msec( + sum( + end - begin + for begin, end in merged_spans( + (cycle["begin_nsec"], cycle["end_nsec"]) for cycle in cycles + ) + ) + ) + + +def _cycle_in_vblank_intervals( + cycles: int, + median_duration: float, + vblank_interval: Optional[float], +) -> Tuple[Optional[float], Optional[str]]: + if not cycles: + return None, "no_cycles" + if vblank_interval is None: + return None, "no_vblank_interval" + if not vblank_interval: + return None, "zero_vblank_interval" + if not median_duration: + return None, "zero_length_cycle" + return median_duration / vblank_interval, None + + +def _prepare_frame_cycle_report( + sysprof_data: Dict[str, Any], + timespan_begin: int, + timespan_end: int, + vblank_interval: Optional[float], +) -> Dict[str, Any]: + cycles = calculate_frame_cycles(sysprof_data, timespan_begin, timespan_end) + durations = [cycle["duration"] for cycle in cycles] + resolved = [cycle for cycle in cycles if cycle["phases"] is not None] + # The phase statistics cover the resolved cycles only, so the duration they are + # shares of has to cover the same ones. Taken from all cycles instead, the four + # shares do not sum to 100%. + resolved_durations = [cycle["duration"] for cycle in resolved] + + duration_statistics = _calculate_statistics(durations) + median_duration = duration_statistics.get("median", 0.0) + analyzed_duration = nsec_to_msec(timespan_end - timespan_begin) + overrunning = [cycle for cycle in resolved if cycle["composition_overrun"] > 0] + intervals_per_cycle, unknown_reason = _cycle_in_vblank_intervals( + len(cycles), median_duration, vblank_interval + ) + return { + "cycles": len(cycles), + "processes": len({cycle["pid"] for cycle in cycles}), + "duration_statistics": duration_statistics, + "implied_fps": 1 / msec_to_sec(median_duration) if median_duration else None, + "coverage": ( + _covered_duration(cycles) / analyzed_duration + if analyzed_duration > 0 + else None + ), + "capture_vblank_interval": vblank_interval, + "vblank_intervals_per_cycle": intervals_per_cycle, + "vblank_intervals_per_cycle_unknown": unknown_reason, + "phase_statistics": { + phase: _calculate_statistics([cycle["phases"][phase] for cycle in resolved]) + for phase in PHASES + }, + "resolved_duration_statistics": _calculate_statistics(resolved_durations), + "overrunning_composition_statistics": _calculate_statistics( + [cycle["composition_overrun"] for cycle in overrunning] + ), + } + + +def _calculate_frame_compositions_per_vblank( + vblanks: Sequence[Dict[str, Any]], compositing_finishes: Sequence[Dict[str, Any]] +) -> List[int]: + """Compositions finished within each interval between two consecutive vblanks.""" + if len(vblanks) < 2: return [] - compositing_finishes = sysprof_data["marks"].get("DidRenderFrame", []) - frame_compositions_per_vblank = [0] - vblanks_iterator = 0 + vblank_ends = [vblank["end_time"] for vblank in vblanks] + compositions_per_interval = [0] * (len(vblanks) - 1) for compositing_finish in compositing_finishes: - while ( - vblanks_iterator < len(vblanks) - and vblanks[vblanks_iterator]["end_time"] <= compositing_finish["end_time"] - ): - vblanks_iterator += 1 - if vblanks_iterator < len(vblanks): - frame_compositions_per_vblank.append(0) - if vblanks_iterator == 0: + # Each interval runs from one refresh to the next, the later one included, + # so a composition ending exactly on a refresh belongs to the interval that + # ends there. + interval = bisect.bisect_left(vblank_ends, compositing_finish["end_time"]) - 1 + if not 0 <= interval < len(compositions_per_interval): + # Composited before the first vblank of the timespan or after the last + # one, so there is no interval between two refreshes it belongs to. continue - frame_compositions_per_vblank[vblanks_iterator - 1] += 1 - - return frame_compositions_per_vblank + compositions_per_interval[interval] += 1 + return compositions_per_interval -def _calculate_vblanks_per_rendering_update(sysprof_data: Dict[str, Any]) -> List[int]: - vblanks = sysprof_data["marks"].get("DisplayLinkUpdate", []) - if len(vblanks) == 0: +def _calculate_vblanks_per_rendering_update( + vblanks: Sequence[Dict[str, Any]], rendering_updates: Sequence[Dict[str, Any]] +) -> List[int]: + """How many display refreshes each rendering update spanned.""" + if not vblanks: return [] - rendering_updates = sysprof_data["marks"]["LayerTreeHostRenderingUpdate"] - vblanks_iterator = 0 - vblanks_per_rendering_update = [] + vblank_ends = [vblank["end_time"] for vblank in vblanks] + counts = [] for rendering_update in rendering_updates: - rendering_update_begin = ( - rendering_update["end_time"] - rendering_update["duration"] - ) - while ( - vblanks_iterator < len(vblanks) - and vblanks[vblanks_iterator]["end_time"] <= rendering_update_begin - ): - vblanks_iterator += 1 - if ( - vblanks_iterator >= len(vblanks) - or vblanks[vblanks_iterator]["end_time"] <= rendering_update_begin - ): - break - if vblanks_iterator == 0: + begin, end = mark_begin(rendering_update), rendering_update["end_time"] + if end < vblank_ends[0] or begin > vblank_ends[-1]: + # Outside the refreshes the timespan holds, so they say nothing about + # this update, rather than saying it spanned none of them. continue - vblanks_during_rendering_update = 0 - while ( - vblanks_iterator < len(vblanks) - and vblanks[vblanks_iterator]["end_time"] <= rendering_update["end_time"] - ): - vblanks_iterator += 1 - vblanks_during_rendering_update += 1 - vblanks_per_rendering_update.append( - 1 + max(vblanks_during_rendering_update - 1, 0) - ) - return vblanks_per_rendering_update + first = bisect.bisect_right(vblank_ends, begin) + counts.append(max(bisect.bisect_right(vblank_ends, end) - first, 1)) + return counts def _calculate_statistics_input_data( @@ -279,45 +369,14 @@ def _calculate_statistics_input_data( return statistics_input_data -def _calculate_statistics(values: Sequence[Union[int, float]]) -> Dict[str, Any]: - if values == []: - return {} - result: Dict[str, Any] = { - "n": len(values), - "min": min(values), - "max": max(values), - "mean": statistics.mean(values), - "stddev": statistics.stdev(values) if len(values) > 1 else 0, - "median": statistics.median(values), - } - if len(values) > 1: - result["percentiles"] = { - k: v - for k, v in dict( - zip(range(1, 100), statistics.quantiles(values, n=100)) - ).items() - if k in RELEVANT_PERCENTILES - } - return result - - -def _render_text_report(report: Dict[str, Any]) -> None: +def _render_text_report(report: Dict[str, Any], explain: bool = False) -> None: print( "Timespan: {:.4f} - {:.4f} [s]".format( - abs(msec_to_sec(report["document"]["timespan"]["begin"])), - abs(msec_to_sec(report["document"]["timespan"]["end"])), - ) - ) - print( - "Duration: {:.4f} [s]".format( - abs( - msec_to_sec( - report["document"]["timespan"]["end"] - - report["document"]["timespan"]["begin"] - ) - ) + msec_to_sec(report["document"]["timespan"]["begin"]), + msec_to_sec(report["document"]["timespan"]["end"]), ) ) + print("Duration: {:.4f} [s]".format(_analyzed_duration_sec(report))) print() print("Rendering:") @@ -333,15 +392,13 @@ def _render_text_report(report: Dict[str, Any]) -> None: ) ) vblanks_per_rendering_update_statistics = _statistics_to_strings( - report["rendering"]["vblanks_per_rendering_update"]["statistics"] + report["rendering"]["vblanks_per_rendering_update"]["statistics"], "{:.4g}" ) print("- vblanks per LayerTreeHostRenderingUpdate: ", end="") print( - "(min: {}, median: {}, P75: {}, P99: {}, max: {})".format( + "(min: {}, {}, max: {})".format( vblanks_per_rendering_update_statistics["min"], - vblanks_per_rendering_update_statistics["median"], - vblanks_per_rendering_update_statistics["percentiles"][75], - vblanks_per_rendering_update_statistics["percentiles"][99], + ", ".join(_percentile_strings(vblanks_per_rendering_update_statistics)), vblanks_per_rendering_update_statistics["max"], ) ) @@ -351,24 +408,40 @@ def _render_text_report(report: Dict[str, Any]) -> None: ) ) print(f"- frames rendered: {report['rendering']['frames_rendered']}") - print(f"- theoretical FPS: {report['rendering']['theoretical_fps']:.2f}") + theoretical_fps = report["rendering"]["theoretical_fps"] + print( + "- theoretical FPS: {}".format( + "{:.2f}".format(theoretical_fps) if theoretical_fps is not None else "-" + ) + ) print("- frame rendering reasons:") for reason in sorted(report["rendering"]["frame_rendering_reasons"].keys()): print( " - {}: {}".format( - reason, report["rendering"]["frame_rendering_reasons"][reason] + explanations.UNNAMED_FRAME_RENDERING_REASONS.get(reason, reason), + report["rendering"]["frame_rendering_reasons"][reason], ) ) frame_compositions_per_vblank_statistics = _statistics_to_strings( - report["rendering"]["frame_compositions_per_vblank_statistics"] + report["rendering"]["frame_compositions_per_vblank_statistics"], "{:.4g}" ) print( - "- frame compositions per vblank: (min: {}, median: {}, max: {})".format( + "- frame compositions per vblank:" + " (min: {}, mean: {}, median: {}, max: {})".format( frame_compositions_per_vblank_statistics["min"], + frame_compositions_per_vblank_statistics["mean"], frame_compositions_per_vblank_statistics["median"], frame_compositions_per_vblank_statistics["max"], ) ) + if explain: + print() + print(_theoretical_fps_explanation(report)) + + _render_frame_cycle_numbers(report["frame_cycle"]) + if explain: + print() + print(explanations.FRAME_CYCLE) print() print("Statistics (durations):") @@ -397,17 +470,9 @@ def _render_text_report(report: Dict[str, Any]) -> None: report, ) ) - print() - print( - " StyleRecalc is an umbrella mark: it nests RenderTreeBuild,\n" - " PerformSubtreesLayout and CompositingUpdate, so its duration is not\n" - " style resolution alone. Subtract the nested marks to get that.\n" - " CompositingUpdate runs twice per rendering update, once inside\n" - " StyleRecalc and once after it, and the second one is much shorter, so\n" - " its percentiles are bimodal rather than centered on a typical value.\n" - " PaintTile runs on the painting threads, so summing it across a frame\n" - " yields aggregate work rather than elapsed time." - ) + if explain: + print() + print(explanations.MARK_STATISTICS) print() print("Statistics (other):") @@ -424,6 +489,170 @@ def _render_text_report(report: Dict[str, Any]) -> None: ) +def _analyzed_duration_sec(report: Dict[str, Any]) -> float: + """How long the analyzed timespan is, in seconds.""" + return msec_to_sec( + report["document"]["timespan"]["end"] - report["document"]["timespan"]["begin"] + ) + + +def _theoretical_fps_explanation(report: Dict[str, Any]) -> str: + rendering = report["rendering"] + if rendering["theoretical_fps"] is None: + return explanations.UNKNOWN_THEORETICAL_FPS.format( + frames=rendering["frames_rendered"], + refresh_rate=_refresh_rate_explanation(report), + ) + return explanations.THEORETICAL_FPS.format( + frames=rendering["frames_rendered"], + duration=_analyzed_duration_sec(report), + fps="{:.2f}".format(rendering["theoretical_fps"]), + refresh_rate=_refresh_rate_explanation(report), + ) + + +def _refresh_rate_explanation(report: Dict[str, Any]) -> str: + rendering = report["rendering"] + interval = rendering["vblank_interval_statistics"].get("median") + vblanks = rendering["vblanks"] + if interval: + refresh_rate = explanations.REFRESH_RATE.format( + rate=1 / msec_to_sec(interval), interval=interval + ) + elif interval is None: + refresh_rate = explanations.REFRESH_RATE_WITHOUT_INTERVAL.format( + vblanks=vblanks, plural="" if vblanks == 1 else "s" + ) + else: + refresh_rate = explanations.REFRESH_RATE_ZERO_INTERVAL.format(vblanks=vblanks) + return refresh_rate + + +def _percentile_strings(strings: Dict[str, Any]) -> List[str]: + """The percentiles of a statistic, and its median among them, in order. + + Takes the strings _statistics_to_strings() built, not the statistic itself. + """ + return [ + ( + "median: {}".format(strings["median"]) + if percentile == PERCENTILE_SHOWN_AS_MEDIAN + else "P{}: {}".format(percentile, strings["percentiles"][percentile]) + ) + for percentile in RELEVANT_PERCENTILES + ] + + +def _analyzed_cycles(frame_cycle: Dict[str, Any]) -> int: + """How many cycles the phase statistics were taken from.""" + return frame_cycle["resolved_duration_statistics"].get("n", 0) + + +def _render_frame_cycle_numbers(frame_cycle: Dict[str, Any]) -> None: + print() + print("Frame cycle:") + print(f"- cycles: {frame_cycle['cycles']}") + if frame_cycle["processes"] > 1: + print( + "- rendered by {} processes, whose frames the medians below" + " describe together".format(frame_cycle["processes"]) + ) + + if frame_cycle["cycles"] == 0: + print( + "- no cycles here: no two consecutive LayerTreeHostRenderingUpdate" + " marks of one process begin within the analyzed timespan" + ) + return + + duration_statistics = _statistics_to_strings(frame_cycle["duration_statistics"]) + print( + "- cycle duration: (min: {}, {}, max: {}) [ms]".format( + duration_statistics["min"], + ", ".join(_percentile_strings(duration_statistics)), + duration_statistics["max"], + ) + ) + implied_fps = frame_cycle["implied_fps"] + print( + "- implied FPS (1000 / median cycle): {}".format( + "{:.2f}".format(implied_fps) if implied_fps is not None else "-" + ) + ) + coverage = frame_cycle["coverage"] + print( + "- cycles cover {} of the analyzed duration".format( + "{:.1f}%".format(coverage * 100) if coverage is not None else "-" + ) + ) + if frame_cycle["vblank_intervals_per_cycle"] is None: + print( + "- median cycle in vblank intervals: - ({})".format( + explanations.UNKNOWN_VBLANK_INTERVALS_PER_CYCLE[ + frame_cycle["vblank_intervals_per_cycle_unknown"] + ] + ) + ) + else: + print( + "- median cycle in vblank intervals: {:.2f}" + " ({:.4f} ms per vblank, from the whole capture)".format( + frame_cycle["vblank_intervals_per_cycle"], + frame_cycle["capture_vblank_interval"], + ) + ) + + # Shares of the median duration of the analyzed cycles, not of all of them, + # since the phase medians cover the analyzed ones only. + median_duration = frame_cycle["resolved_duration_statistics"].get("median", 0) + analyzed = "{} of {} cycles analyzed".format( + _analyzed_cycles(frame_cycle), frame_cycle["cycles"] + ) + # Each phase is its own median, and medians do not add up, so the shares below + # need not total 100%. + if median_duration: + analyzed += ", median analyzed cycle {:.4f} ms".format(median_duration) + print(f"- phases (median, {analyzed}):") + shares_shown = False + for phase in PHASES: + label = phase.replace("_", " ") + phase_statistics = frame_cycle["phase_statistics"][phase] + if not phase_statistics: + print(f" - {label}: -") + continue + share = "-" + if median_duration: + share = "{:.1f}%".format(phase_statistics["median"] / median_duration * 100) + shares_shown = True + print( + " - {}: {:.4f} ms ({} of the cycle)".format( + label, phase_statistics["median"], share + ) + ) + if shares_shown: + print( + " (each phase is a median of its own," + " so the shares need not total 100%)" + ) + overrun_statistics = frame_cycle["overrunning_composition_statistics"] + if not _analyzed_cycles(frame_cycle): + print("- composition overrunning the cycle: -") + elif overrun_statistics: + print( + "- composition overrunning the cycle: in {} of {} analyzed cycles," + " by {:.4f} ms median".format( + overrun_statistics["n"], + _analyzed_cycles(frame_cycle), + overrun_statistics["median"], + ) + ) + else: + print( + "- composition overrunning the cycle: in none of {} analyzed" + " cycles".format(_analyzed_cycles(frame_cycle)) + ) + + def _render_statistics_rows_to_stdout(statistics_rows: List[List[str]]) -> None: column_max_sizes = [ max([len(row[col]) for row in statistics_rows]) @@ -460,28 +689,34 @@ def _prepare_statistics_row( ] -def _statistics_to_strings(statistics: Dict[str, Any]) -> Dict[str, Any]: +def _statistics_to_strings( + statistic: Dict[str, Any], number: str = "{:.4f}" +) -> Dict[str, Any]: + """The numbers of a statistic as strings, `-` where it holds none.""" + def format_statistic( data: Dict[str, Any], key: str, default: str = "-", - subkey: Optional[int] = None, + subkey: Optional[str] = None, ) -> str: if key not in data: return default - number = data[key] - if subkey is not None and subkey in number: - number = data[key][subkey] + value = data[key] + if subkey is not None: + if subkey not in value: + return default + value = value[subkey] if key == "n": - return str(number) - return "{:.4f}".format(number) + return str(value) + return number.format(value) return { - key: format_statistic(statistics, key, "0" if key == "n" else "-") + key: format_statistic(statistic, key, "0" if key == "n" else "-") for key in ["n", "min", "max", "mean", "stddev", "median"] } | { "percentiles": { - percentile: format_statistic(statistics, "percentiles", "-", percentile) + percentile: format_statistic(statistic, "percentiles", "-", str(percentile)) for percentile in RELEVANT_PERCENTILES } } diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/explanations.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/explanations.py new file mode 100644 index 000000000000..2ff83543cc79 --- /dev/null +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/explanations.py @@ -0,0 +1,110 @@ +"""The paragraphs `analyze --explain` prints, kept apart from the analysis itself. + +The long form of what the report means. The README keeps the same caveats short. +""" + +THEORETICAL_FPS = """\ + Theoretical FPS is the number of DidRenderFrame marks, one per + composition, divided by the analyzed duration: + {frames} / {duration:.4f} s = {fps}. It is not clamped to the refresh + rate, and it counts every process, so two renderers compositing once + per refresh read as two. +{refresh_rate} + Matching it means completing one frame per vblank interval. The frame + cycle below shows where a frame's time goes.""" + +UNKNOWN_THEORETICAL_FPS = """\ + Theoretical FPS is the number of DidRenderFrame marks, one per + composition, divided by the analyzed duration. It is unknown here: the + analyzed timespan has no length to divide the {frames} of them by. +{refresh_rate}""" + +REFRESH_RATE = """\ + That rate is {rate:.2f} Hz here, from the median vblank interval of + {interval:.4f} ms.""" + +REFRESH_RATE_WITHOUT_INTERVAL = """\ + That rate is unknown here: the display link refreshed {vblanks} time{plural}, + too few for an interval between two refreshes.""" + +REFRESH_RATE_ZERO_INTERVAL = """\ + That rate is unknown here: the median interval between two of the {vblanks} + refreshes is zero.""" + +FRAME_CYCLE = """\ + A frame cycle starts when a LayerTreeHostRenderingUpdate begins and + ends when the next one begins, so consecutive cycles tile a rendering + period without gaps and the frame rate within it is 1 / cycle + duration. The phases are: + - rendering update: the LayerTreeHostRenderingUpdate mark itself, + i.e. requestAnimationFrame callbacks, style recalc, layout and the + compositing update. + - compositing: the time after the rendering update spent in + RenderLayerTree, FlushCompositingState, PaintToGLContext or + WaitForCompositionCompletion. They are merged as spans rather than + added up, since they nest and a cycle can composite twice with a + wait in between. On GTK and WPE the wait for the GPU and the buffer + handover after it both happen inside RenderLayerTree, so getting the + frame out is counted here too. + - waiting for compositing: what is left of the cycle once the other + phases are taken off it, mostly spent waiting for the painting + threads to finish their tiles. + - idle: from the end of compositing until the next rendering update + begins. + Each phase is its own median over the cycles it could be read from, so + the shares need not total 100%. + The loop is vblank-driven, but it does not wait for a refresh it has + already missed: a cycle that runs past its vblank deadline schedules + the next rendering update as soon as compositing is far enough along + rather than at the following refresh, so a cycle longer than one vblank + interval means frames cannot be presented on every vblank. Compositing + that began in one cycle can then still be running when the next + rendering update opens the following one, and since the phases add up + to the cycle exactly, that part of it past the cycle end is reported on + its own as composition overrunning the cycle rather than as a fifth + phase. + Implied FPS is the rate of the median cycle, while theoretical FPS + averages over the whole analyzed duration, so the two differ by the + time no cycle spans, which coverage reports, plus the time the cycles + spent idle. A capture that renders for a stretch and then sits still, + rather than rendering evenly throughout, keeps a high coverage, since + the pause is itself one long cycle, and a high idle share along with + it. + A cycle reaching past either end of the analyzed timespan is left out + of it. Between two rendering periods the engine idles, and that gap is + reported as one long, nearly all-idle cycle rather than dropped. + A cycle only ever runs between two rendering updates of one process. + Where more than one rendered, their cycles are reported together and + the count above says so. The refreshes come from the UI process and + name no display, so a capture of two displays refreshing at once halves + the interval between them and doubles the rate derived from it.""" + + +MARK_STATISTICS = """\ + StyleRecalc is an umbrella mark: RenderTreeBuild, the CompositingUpdate + that follows a style change, and layout where style resolution + interleaves it all run inside it, so its duration is not style + resolution alone. Subtract the nested marks to get that. + CompositingUpdate is emitted after a style change, after layout and on + scrolling, and those runs are of very different lengths, so its + percentiles mix them rather than centering on a typical value. + PaintTile runs on the painting threads, so summing it across a frame + yields aggregate work rather than elapsed time. + Percentiles are interpolated between the samples, so they lie between + the minimum and the maximum, and read - below two samples.""" + + +# What the report says of a frame whose message named no reason for compositing it, +# keyed by the token _frame_rendering_reason() buckets it under. +UNNAMED_FRAME_RENDERING_REASONS = { + "_none": "none given", +} + +# What the report says of a median cycle it cannot be given in vblank intervals, +# keyed by the token _cycle_in_vblank_intervals() reports it with. +UNKNOWN_VBLANK_INTERVALS_PER_CYCLE = { + "no_cycles": "there are no cycles", + "no_vblank_interval": "no vblank interval known", + "zero_vblank_interval": "the vblank interval is zero", + "zero_length_cycle": "the median cycle is of zero length", +} diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py new file mode 100644 index 000000000000..0859a91c21eb --- /dev/null +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py @@ -0,0 +1,258 @@ +"""Frame cycle reconstruction for the `analyze` command. + +What a cycle is, what its phases mean and how to read the numbers taken from them +is what `analyze --explain` prints, from webkitsysprof.analyze.explanations, which +is where that is written down rather than here. +""" + +import bisect +import itertools +from typing import Any, Dict, List, Optional, Sequence, Tuple + +from ..utils import mark_begin, marks_by_process, merged_spans, nsec_to_msec + +# Compositing of a cycle is the time any of these marks was running within it, +# whether the mark began there or in an earlier cycle it overran. All four are read, +# and as time rather than by name, because which of them a cycle carries varies: +# renderLayerTree() opens its trace scope before the checks that can return early, so +# a cycle can hold a RenderLayerTree mark with no flush or paint inside it. Their +# spans are merged, so listing a mark that RenderLayerTree already encloses adds no +# time of its own. +COMPOSITING_MARKS = [ + "RenderLayerTree", + "FlushCompositingState", + "PaintToGLContext", + "WaitForCompositionCompletion", +] +PHASES = [ + "rendering_update", + "waiting_for_compositing", + "compositing", + "idle", +] +RENDERING_UPDATE, WAITING, COMPOSITING, IDLE = PHASES + + +class _MarkIndex: + """Marks of a single name, ordered by begin time for lookup within a cycle.""" + + def __init__(self, marks: Sequence[Dict[str, Any]]) -> None: + self._marks = sorted(marks, key=mark_begin) + self._begins = [mark_begin(mark) for mark in self._marks] + # The latest end among the first i marks. Every mark still running at a + # given time covers that time onwards, so their union is one span reaching + # to the latest of their ends, and that is all this has to answer. + self._ends_until: List[int] = list( + itertools.accumulate((mark["end_time"] for mark in self._marks), max) + ) + + def spans_within(self, begin: int, end: int) -> List[Tuple[int, int]]: + """The [begin, end) spans of time a mark of this name was running. + + Unmerged and in begin order. What began before the window and had not ended + by then counts, since a composition of an earlier cycle is what a cycle + waiting on the compositor is running, and all of those together reach from + the start of the window to the latest of their ends. + """ + first = bisect.bisect_left(self._begins, begin) + spans = [] + still_running = self._ends_until[first - 1] if first else None + if still_running is not None and still_running > begin: + spans.append((begin, min(still_running, end))) + for index in range(first, len(self._marks)): + if self._begins[index] >= end: + break + spans.append( + (self._begins[index], min(self._marks[index]["end_time"], end)) + ) + return spans + + def last_end_of_marks_beginning_within(self, begin: int, end: int) -> Optional[int]: + """End of the last mark beginning within [begin, end), None if there is none. + + The last end, not the first: marks nest, so the first one to end is not the + one compositing finished with. Indexing by end time instead has the mirror + problem, of returning a short mark of the next cycle when an enclosing mark + overruns this one. + """ + first = bisect.bisect_left(self._begins, begin) + last = bisect.bisect_left(self._begins, end) + if first >= last: + return None + return max(self._marks[index]["end_time"] for index in range(first, last)) + + +def _marks_beginning_within( + marks: Sequence[Dict[str, Any]], + begin: Optional[int], + end: Optional[int], + including_end: bool = False, +) -> List[Dict[str, Any]]: + """The marks that begin within the window, which are the only ones read.""" + + def within(mark: Dict[str, Any]) -> bool: + if begin is not None and mark_begin(mark) < begin: + return False + if end is None: + return True + return mark_begin(mark) <= end if including_end else mark_begin(mark) < end + + return [mark for mark in marks if within(mark)] + + +def _compositing_indices_by_process( + sysprof_data: Dict[str, Any], + timespan_begin: Optional[int], + timespan_end: Optional[int], +) -> Dict[int, List[_MarkIndex]]: + """The compositing marks of the capture, grouped by process and by name.""" + by_pid: Dict[int, Dict[str, List[Dict[str, Any]]]] = {} + for mark_name in COMPOSITING_MARKS: + marks = [ + mark + for mark in _marks_beginning_within( + sysprof_data["marks"].get(mark_name, []), None, timespan_end + ) + if timespan_begin is None or mark["end_time"] >= timespan_begin + ] + for pid, process_marks in marks_by_process(marks).items(): + by_pid.setdefault(pid, {})[mark_name] = process_marks + return { + pid: [_MarkIndex(marks.get(mark_name, [])) for mark_name in COMPOSITING_MARKS] + for pid, marks in by_pid.items() + } + + +def _compositing_spans( + indices: Sequence[_MarkIndex], begin: int, end: int +) -> List[Tuple[int, int]]: + """The spans of [begin, end) that were spent compositing, merged and in order. + + Merged rather than taken as one span from the first mark to the last, because a + cycle can composite twice: the tail of a composition it inherited and then one + of its own, with the wait for the painting threads in between. Reading that as + one span would report the wait as compositing. + """ + return merged_spans( + span for index in indices for span in index.spans_within(begin, end) + ) + + +def _own_compositing_end( + indices: Sequence[_MarkIndex], cycle_begin: int, cycle_end: int +) -> Optional[int]: + """When the composition of this cycle was over, None where it has none. + + Only the marks that began within the cycle count, so this is the cycle's own + composition rather than one it inherited. It may end after the cycle does: when + compositing overruns, the next rendering update is dispatched from within the + composition wait. It is usually the RenderLayerTree mark compositing began with, + since that encloses the others and runs on through the wait for the GPU to + finish the frame, which costs frame time as well. + """ + ends = [ + index.last_end_of_marks_beginning_within(cycle_begin, cycle_end) + for index in indices + ] + ended = [end for end in ends if end is not None] + return max(ended) if ended else None + + +def calculate_frame_cycles( + sysprof_data: Dict[str, Any], + timespan_begin: Optional[int] = None, + timespan_end: Optional[int] = None, +) -> List[Dict[str, Any]]: + """Split the timeline into frame cycles and break each cycle into its phases. + + Each cycle carries its boundaries in nanoseconds, its duration and phases in + milliseconds, and the process it belongs to. The phases sum to the cycle + duration exactly, so compositing running past the cycle is reported beside them + as `composition_overrun` rather than as a fifth phase. A cycle nothing + composited in has both set to None, so callers can count it without letting it + skew the per-phase statistics. + + A cycle runs between two rendering updates of one process, which the marks name. + `timespan_begin`/`timespan_end` keep the cycles + lying entirely within that window, in nanoseconds, the way trimming keeps the + marks lying entirely within it. Filtering here rather than trimming the marks + first keeps the compositing marks of a cycle at the window edge, which trimming + drops for reaching past it. `analyze --explain` reads all of this out. + """ + # A cycle ends where the next update begins, and the whole cycle has to fit in + # the window, so an update beginning on the very edge still bounds one. + all_updates = _marks_beginning_within( + sysprof_data["marks"].get("LayerTreeHostRenderingUpdate", []), + timespan_begin, + timespan_end, + including_end=True, + ) + compositing = _compositing_indices_by_process( + sysprof_data, timespan_begin, timespan_end + ) + cycles: List[Dict[str, Any]] = [] + for pid, updates in marks_by_process(all_updates).items(): + cycles += _cycles_of_one_process( + pid, sorted(updates, key=mark_begin), compositing.get(pid, []) + ) + return sorted(cycles, key=lambda cycle: cycle["begin_nsec"]) + + +def _cycles_of_one_process( + pid: int, updates: Sequence[Dict[str, Any]], indices: Sequence[_MarkIndex] +) -> List[Dict[str, Any]]: + """The cycles between the given rendering updates, all of one process. + + `indices` holds that same process's compositing marks, one index per name. + """ + cycles: List[Dict[str, Any]] = [] + for update, next_update in zip(updates, itertools.islice(updates, 1, None)): + cycle_begin = mark_begin(update) + cycle_end = mark_begin(next_update) + cycle: Dict[str, Any] = { + "pid": pid, + "begin_nsec": cycle_begin, + "end_nsec": cycle_end, + "duration": nsec_to_msec(cycle_end - cycle_begin), + "phases": None, + "composition_overrun": None, + } + cycles.append(cycle) + + # LayerTreeHost::updateRendering() asserts it is not re-entered, so an + # update cannot outlast its own cycle. The phases cover the cycle, so one + # that does anyway is cut off rather than trusted. + update_end = min(update["end_time"], cycle_end) + # What was traced as compositing within the cycle, whether it began there + # or in an earlier one. A composition running entirely beside the rendering + # update still says the cycle composited, so it is what decides that, while + # the phases below cover the time after the update. + within = _compositing_spans(indices, cycle_begin, cycle_end) + if not within: + # Nothing about compositing can be said of this cycle. + continue + spans = [ + (max(span_begin, update_end), span_end) + for span_begin, span_end in within + if span_end > update_end + ] + own_end = _own_compositing_end(indices, cycle_begin, cycle_end) + + compositing = sum(span_end - span_begin for span_begin, span_end in spans) + # Idle runs from the last thing traced until the next update begins, and + # what is neither the update, compositing nor idle was spent waiting. + idle = cycle_end - (spans[-1][1] if spans else update_end) + cycle["phases"] = { + RENDERING_UPDATE: nsec_to_msec(update_end - cycle_begin), + WAITING: nsec_to_msec(cycle_end - update_end - compositing - idle), + COMPOSITING: nsec_to_msec(compositing), + IDLE: nsec_to_msec(idle), + } + # How far the compositing this cycle began ran past its end. A composition + # of an earlier cycle running through this one is that cycle's, not this + # one's, so what began here is what counts. + cycle["composition_overrun"] = ( + nsec_to_msec(max(own_end - cycle_end, 0)) if own_end is not None else 0.0 + ) + + return cycles diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/dump/__init__.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/dump/__init__.py index 09c0aa497358..e5f7d0abbfcf 100644 --- a/Tools/Scripts/webkit-sysprof/webkitsysprof/dump/__init__.py +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/dump/__init__.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List from ..parser import parse +from ..utils import mark_begin, mark_pid def dump(args: argparse.Namespace) -> None: @@ -12,7 +13,7 @@ def dump(args: argparse.Namespace) -> None: headers = ["category", "name", "description", "time", "offset", "value"] rows = _counters_to_rows(data["counters"]) else: - headers = ["group", "name", "message", "time", "duration", "end_time"] + headers = ["group", "pid", "name", "message", "time", "duration", "end_time"] rows = _marks_to_rows(data["marks"]) if args.format == "json": @@ -25,9 +26,10 @@ def _marks_to_rows(marks: List[Dict[str, Any]]) -> List[Dict[str, Any]]: return [ { "group": mark["group"], + "pid": mark_pid(mark), "name": mark["name"], "message": mark["message"], - "time": mark["end_time"] - mark["duration"], + "time": mark_begin(mark), "duration": mark["duration"], "end_time": mark["end_time"], } diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/histogram/__init__.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/histogram/__init__.py index 24c919368d9c..44ad53945691 100644 --- a/Tools/Scripts/webkit-sysprof/webkitsysprof/histogram/__init__.py +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/histogram/__init__.py @@ -1,24 +1,31 @@ import argparse import math -import statistics from typing import Any, Dict, List from ..parser import parse from ..utils import ( MSEC_PER_SEC, - nsec_to_msec, + check_timespan_holds_data, + intervals_between_marks, + marks_by_process, + marks_in_time_order, + percentiles, + sample_statistics, parse_timespan_argument, - trim_sysprof_data_to_timespan, + sysprof_data_with_marks_by_name, + trim_marks_by_name_to_timespan, ) def delta_histogram(args: argparse.Namespace) -> None: timespan_begin, timespan_end = parse_timespan_argument(args.timespan) parsed_data = parse(args.capture_file, marks=True, counters=False) - trimmed_data = trim_sysprof_data_to_timespan( - parsed_data, timespan_begin, timespan_end + capture_begin, capture_end = parsed_data["document"]["timespan"] + check_timespan_holds_data(capture_begin, capture_end, timespan_begin, timespan_end) + trimmed_data = trim_marks_by_name_to_timespan( + sysprof_data_with_marks_by_name(parsed_data), timespan_begin, timespan_end ) - deltas_ms = _calculate_delta_times_ms(trimmed_data["marks"], args.mark_type) + deltas_ms = _delta_times_ms(trimmed_data, args.mark_type) if not deltas_ms: print(f"No data available for mark: {args.mark_type}") @@ -27,27 +34,25 @@ def delta_histogram(args: argparse.Namespace) -> None: _plot_delta_time_distribution(args.mark_type, deltas_ms) -def _calculate_delta_times_ms( - marks: List[Dict[str, Any]], mark_name: str -) -> List[float]: - end_times = sorted(mark["end_time"] for mark in marks if mark["name"] == mark_name) - if len(end_times) < 2: - return [] +def _delta_times_ms(sysprof_data: Dict[str, Any], mark_name: str) -> List[float]: + """Times between consecutive marks of that name, in milliseconds.""" return [ - nsec_to_msec(end_times[i] - end_times[i - 1]) for i in range(1, len(end_times)) + delta + for marks in marks_by_process( + marks_in_time_order(sysprof_data, mark_name) + ).values() + for delta in intervals_between_marks(marks) ] def _calculate_optimal_bins(deltas_ms: List[float]) -> int: # Freedman-Diaconis rule, falling back to Sturges' rule for a tiny IQR. - if len(deltas_ms) < 2: + low, high = 25, 75 + quartiles = percentiles(deltas_ms, [low, high], method="exclusive") + if not quartiles: + # Too few deltas for a quartile, so there is no spread to bin by. return 10 - - q25, q75 = ( - statistics.quantiles(deltas_ms, n=4)[0], - statistics.quantiles(deltas_ms, n=4)[2], - ) - iqr = q75 - q25 + iqr = quartiles[str(high)] - quartiles[str(low)] if iqr > 0: n = len(deltas_ms) @@ -62,17 +67,20 @@ def _calculate_optimal_bins(deltas_ms: List[float]) -> int: def _plot_delta_time_distribution(mark_name: str, deltas_ms: List[float]) -> None: import matplotlib.pyplot as plt - mean_val = statistics.mean(deltas_ms) - median_val = statistics.median(deltas_ms) - min_val = min(deltas_ms) - max_val = max(deltas_ms) - std_val = statistics.stdev(deltas_ms) if len(deltas_ms) > 1 else 0.0 + stats = sample_statistics(deltas_ms) + mean_val = stats["mean"] + median_val = stats["median"] + min_val = stats["min"] + max_val = stats["max"] + std_val = stats["stddev"] n_bins = _calculate_optimal_bins(deltas_ms) + # Marks sharing a timestamp are no time apart, and no frequency either. + frequency = f"{MSEC_PER_SEC / mean_val:.1f} Hz" if mean_val else "-" stats_text = ( f"Sample Size: {len(deltas_ms):,}\n" f"Std Dev: {std_val:.2f} ms\n" - f"Frequency: {MSEC_PER_SEC / mean_val:.1f} Hz" + f"Frequency: {frequency}" ) fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 12), gridspec_kw={"hspace": 0.15}) diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/parser/direct_parser.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/parser/direct_parser.py index cc18bb4cd443..2bd56c5d6569 100644 --- a/Tools/Scripts/webkit-sysprof/webkitsysprof/parser/direct_parser.py +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/parser/direct_parser.py @@ -112,6 +112,10 @@ def _index_frames(data: mmap.mmap) -> List[Tuple[int, int, int, int]]: def _parse_mark( data: mmap.mmap, offset: int, length: int, duration: int, end_time: int ) -> Dict[str, Any]: + # The frame header is len, cpu, pid, time, so the process that emitted the mark + # is four bytes in. The group names its kind, e.g. "WebKit (Web)", which two + # processes of one kind share. + (pid,) = struct.unpack_from(" None print(f"Subtitle: {document['subtitle']}") print( "Timespan: {:.4f} - {:.4f} [s]".format( - abs(nsec_to_sec(document["timespan"][0])), - abs(nsec_to_sec(document["timespan"][1])), + nsec_to_sec(document["timespan"][0]), + nsec_to_sec(document["timespan"][1]), ) ) print() diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/frame_cycles_unittest.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/frame_cycles_unittest.py new file mode 100644 index 000000000000..3392147e4d5f --- /dev/null +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/frame_cycles_unittest.py @@ -0,0 +1,709 @@ +"""Frame cycle reconstruction on synthetic captures. + +The sample capture only covers cycles that all resolve. These build the awkward +cases by hand. +""" + +import unittest + +from .helpers import SysprofTestCase, approx, mark, sysprof_data + +from webkitsysprof import analyze +from webkitsysprof.cycles import calculate_frame_cycles +from webkitsysprof.utils import ( + display_refreshes, + median_vblank_interval, + msec_to_nsec, +) + + +def frame_cycle_report(data, begin_msec=0, end_msec=1000, vblank_interval=None): + return analyze._prepare_frame_cycle_report( + data, msec_to_nsec(begin_msec), msec_to_nsec(end_msec), vblank_interval + ) + + +class FrameCyclesTest(SysprofTestCase): + def test_phases_partition_every_resolved_cycle(self): + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 14), + mark("PaintToGLContext", 10, 13), + mark("LayerTreeHostRenderingUpdate", 16, 20), + mark("RenderLayerTree", 22, 30), + mark("PaintToGLContext", 25, 29), + mark("LayerTreeHostRenderingUpdate", 32, 36), + ] + ) + + cycles = calculate_frame_cycles(data) + self.assertEqual(len(cycles), 2) + for cycle in cycles: + self.assertIsNotNone(cycle["phases"]) + self.assertEqual(sum(cycle["phases"].values()), approx(cycle["duration"])) + + def test_composition_ending_after_the_cycle_is_reported_as_an_overrun(self): + # RenderLayerTree runs on past PaintToGLContext to wait for the GPU to finish + # the frame, and the next rendering update starts during that wait. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 30), + mark("PaintToGLContext", 10, 13), + mark("WaitForCompositionCompletion", 13, 30), + mark("LayerTreeHostRenderingUpdate", 20, 25), + ] + ) + + cycle = calculate_frame_cycles(data)[0] + self.assertIsNotNone(cycle["phases"]) + # Compositing covers that wait up to the cycle end, the rest counts as an + # overrun rather than being dropped or booked as idle. + self.assertEqual(cycle["phases"]["compositing"], approx(13.0)) + self.assertEqual(cycle["phases"]["idle"], approx(0.0)) + self.assertEqual(cycle["composition_overrun"], approx(10.0)) + self.assertEqual(sum(cycle["phases"].values()), approx(cycle["duration"])) + + def test_a_cycle_after_an_overrunning_composition_still_resolves(self): + # The first cycle composites past the start of the second. Picking the end mark + # in begin order hands that long one to the second cycle too, which used to drop + # exactly the slow frames worth looking at. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 6, 56), + mark("PaintToGLContext", 10, 50), + mark("LayerTreeHostRenderingUpdate", 18, 20), + mark("RenderLayerTree", 22, 23), + mark("PaintToGLContext", 22, 23), + mark("LayerTreeHostRenderingUpdate", 30, 35), + ] + ) + + cycles = calculate_frame_cycles(data) + self.assertEqual( + [cycle["phases"] is not None for cycle in cycles], [True, True] + ) + # The composition begun at 6 ms is still running through the second cycle, so + # that cycle is compositing from the end of its update to its own end. + self.assertEqual(cycles[1]["phases"]["compositing"], approx(10.0)) + self.assertEqual(cycles[1]["phases"]["idle"], approx(0.0)) + # Each cycle reports how far its own composition ran past it, so the long one + # counts for the cycle it began in and the short one for the next. + self.assertEqual(cycles[0]["composition_overrun"], approx(38.0)) + self.assertEqual(cycles[1]["composition_overrun"], approx(0.0)) + + def test_cycles_without_compositing_marks_are_counted_but_not_analyzed(self): + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 14), + mark("PaintToGLContext", 10, 13), + mark("LayerTreeHostRenderingUpdate", 16, 20), + mark("LayerTreeHostRenderingUpdate", 24, 28), + mark("RenderLayerTree", 30, 36), + mark("PaintToGLContext", 32, 35), + mark("LayerTreeHostRenderingUpdate", 40, 44), + ] + ) + + report = frame_cycle_report(data) + self.assertEqual(report["cycles"], 3) + self.assertEqual(report["resolved_duration_statistics"]["n"], 2) + # The phase statistics cover the two cycles that composited, not all three. + self.assertEqual(report["resolved_duration_statistics"]["n"], 2) + self.assertEqual(report["duration_statistics"]["n"], 3) + + def test_the_gap_between_two_rendering_periods_is_reported_as_a_long_cycle(self): + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 14), + mark("PaintToGLContext", 10, 13), + mark("LayerTreeHostRenderingUpdate", 16, 20), + mark("RenderLayerTree", 22, 30), + mark("PaintToGLContext", 25, 29), + mark("LayerTreeHostRenderingUpdate", 500, 505), + mark("RenderLayerTree", 507, 514), + mark("PaintToGLContext", 510, 513), + mark("LayerTreeHostRenderingUpdate", 516, 520), + ] + ) + + report = frame_cycle_report(data) + # Dropping the 470 ms of idle time between the two bursts would hide it. The + # median is what the frames took, and the idle spell is the maximum and the + # idle phase, where it can be read as what it is. + self.assertEqual(report["cycles"], 3) + self.assertEqual(report["duration_statistics"]["median"], approx(16.0)) + self.assertEqual(report["duration_statistics"]["max"], approx(484.0)) + self.assertEqual(report["phase_statistics"]["idle"]["max"], approx(470.0)) + + def test_the_compositing_mark_name_is_resolved_per_cycle(self): + # renderLayerTree() opens its trace scope before the checks that can return + # early, so the first cycle holds a RenderLayerTree with nothing inside it. + # The marks the second cycle carries must not decide for the first. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 14), + mark("LayerTreeHostRenderingUpdate", 16, 20), + mark("RenderLayerTree", 22, 30), + mark("FlushCompositingState", 23, 25), + mark("PaintToGLContext", 25, 29), + mark("LayerTreeHostRenderingUpdate", 32, 36), + ] + ) + + cycles = calculate_frame_cycles(data) + self.assertEqual( + [cycle["phases"] is not None for cycle in cycles], [True, True] + ) + self.assertEqual(cycles[0]["phases"]["compositing"], approx(7.0)) + self.assertEqual(cycles[1]["phases"]["compositing"], approx(8.0)) + + def test_compositing_is_over_when_the_last_compositing_mark_is(self): + # A second RenderLayerTree after PaintToGLContext. Consulting only the first + # mark name that matches would book its 29 ms as idle. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 6, 10), + mark("PaintToGLContext", 7, 8), + mark("RenderLayerTree", 11, 40), + mark("LayerTreeHostRenderingUpdate", 50, 55), + ] + ) + + phases = calculate_frame_cycles(data)[0]["phases"] + # 6 to 10 and 11 to 40, so the millisecond between the two is a wait rather + # than compositing. + self.assertEqual(phases["compositing"], approx(33.0)) + self.assertEqual(phases["waiting_for_compositing"], approx(2.0)) + self.assertEqual(phases["idle"], approx(10.0)) + + def test_compositing_overlapping_the_rendering_update_still_counts(self): + # RenderLayerTree starts on the compositing thread 1 ms before the rendering + # update mark ends. Skipping it would measure the cycle off PaintToGLContext. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 10), + mark("RenderLayerTree", 9, 20), + mark("PaintToGLContext", 12, 18), + mark("LayerTreeHostRenderingUpdate", 30, 35), + ] + ) + + phases = calculate_frame_cycles(data)[0]["phases"] + self.assertEqual(phases["rendering_update"], approx(10.0)) + self.assertEqual(phases["waiting_for_compositing"], approx(0.0)) + self.assertEqual(phases["compositing"], approx(10.0)) + self.assertEqual(phases["idle"], approx(10.0)) + + def test_a_cycle_reaching_past_the_timespan_is_left_out_of_it(self): + # Like a mark crossing the boundary, which trimming drops: a frame half + # outside the window must not contribute a whole frame's timing to it. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 190), + mark("PaintToGLContext", 10, 180), + mark("LayerTreeHostRenderingUpdate", 200, 205), + ], + end_msec=10, + ) + + report = frame_cycle_report(data, end_msec=10) + self.assertEqual(report["cycles"], 0) + self.assertEqual(report["coverage"], approx(0.0)) + + def test_a_capture_without_compositing_marks_keeps_its_cycles(self): + # Rendering updates that composited nothing, so no cycle can be split into + # phases. The cycles themselves are still measured and reported. + marks = [ + mark("LayerTreeHostRenderingUpdate", i * 50, i * 50 + 8) for i in range(10) + ] + marks += [ + mark("DisplayLinkUpdate", i * 16.67, i * 16.67 + 0.1) for i in range(60) + ] + + report = frame_cycle_report(sysprof_data(marks)) + self.assertEqual(report["cycles"], 9) + self.assertEqual(report["resolved_duration_statistics"], {}) + self.assertEqual(report["duration_statistics"]["median"], approx(50.0)) + + def test_coverage_is_the_share_of_the_window_the_analyzed_cycles_took(self): + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 90), + mark("PaintToGLContext", 10, 88), + mark("LayerTreeHostRenderingUpdate", 100, 105), + mark("RenderLayerTree", 107, 190), + mark("PaintToGLContext", 110, 188), + mark("LayerTreeHostRenderingUpdate", 200, 205), + ] + ) + + report = frame_cycle_report(data, begin_msec=100, end_msec=300) + # Only the second cycle lies within the window, and it fills half of it. + self.assertEqual(report["cycles"], 1) + self.assertEqual(report["coverage"], approx(0.5)) + + def test_a_single_cycle_reports_no_percentiles_rather_than_failing(self): + # statistics.quantiles() raises below two data points before Python 3.13. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 14), + mark("PaintToGLContext", 10, 13), + mark("LayerTreeHostRenderingUpdate", 16, 20), + ] + ) + + report = frame_cycle_report(data) + self.assertEqual(report["cycles"], 1) + self.assertEqual(report["duration_statistics"]["percentiles"], {}) + self.assertEqual(report["phase_statistics"]["compositing"]["percentiles"], {}) + + def test_no_cycles_at_all(self): + report = frame_cycle_report( + sysprof_data([mark("LayerTreeHostRenderingUpdate", 0, 5)]) + ) + self.assertEqual(report["cycles"], 0) + self.assertEqual(report["resolved_duration_statistics"], {}) + self.assertEqual(report["duration_statistics"], {}) + # No cycle is no rate, and reporting 0 would read as an infinitely slow one. + self.assertIsNone(report["implied_fps"]) + self.assertIsNone(report["vblank_intervals_per_cycle"]) + + def test_cycles_per_vblank_interval_is_unknown_without_vblank_marks(self): + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 14), + mark("PaintToGLContext", 10, 13), + mark("LayerTreeHostRenderingUpdate", 16, 20), + ] + ) + + self.assertIsNone( + median_vblank_interval(display_refreshes(data)) + ) + # Reporting 0 here would read as "the cycle fits in zero vblank intervals". + self.assertIsNone(frame_cycle_report(data)["vblank_intervals_per_cycle"]) + + def test_cycles_are_restricted_to_the_ones_lying_within_the_timespan(self): + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 7, 14), + mark("PaintToGLContext", 10, 13), + mark("LayerTreeHostRenderingUpdate", 100, 105), + mark("RenderLayerTree", 107, 114), + mark("PaintToGLContext", 110, 113), + mark("LayerTreeHostRenderingUpdate", 200, 205), + ] + ) + + self.assertEqual(len(calculate_frame_cycles(data)), 2) + # 50 ms falls inside the first cycle, so that cycle belongs to neither side. + self.assertEqual(len(calculate_frame_cycles(data, msec_to_nsec(50), None)), 1) + self.assertEqual(len(calculate_frame_cycles(data, None, msec_to_nsec(50))), 0) + self.assertEqual(len(calculate_frame_cycles(data, None, msec_to_nsec(100))), 1) + self.assertEqual( + calculate_frame_cycles(data, msec_to_nsec(50), None)[0]["begin_nsec"], + msec_to_nsec(100), + ) + + def test_compositing_running_inside_the_rendering_update_still_resolves(self): + # The compositing thread finished while the rendering update mark was still + # open. There is no compositing phase to report, but the cycle is measured. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 100), + mark("RenderLayerTree", 10, 50), + mark("LayerTreeHostRenderingUpdate", 200, 205), + ] + ) + + cycle = calculate_frame_cycles(data)[0] + self.assertEqual( + cycle["phases"], + { + "rendering_update": approx(100.0), + "waiting_for_compositing": approx(0.0), + "compositing": approx(0.0), + "idle": approx(100.0), + }, + ) + + def test_a_cycle_never_runs_between_two_processes(self): + # Two web processes rendering at once, which a system-wide capture holds. A + # cycle spanning both would be a frame of neither. + marks = [] + for i in range(3): + marks += [ + mark("LayerTreeHostRenderingUpdate", i * 20, i * 20 + 5, pid=1), + mark("RenderLayerTree", i * 20 + 6, i * 20 + 12, pid=1), + mark("LayerTreeHostRenderingUpdate", i * 20 + 10, i * 20 + 15, pid=2), + mark("RenderLayerTree", i * 20 + 16, i * 20 + 19, pid=2), + ] + + cycles = calculate_frame_cycles(sysprof_data(marks)) + self.assertEqual(len(cycles), 4) + self.assertEqual( + [cycle["duration"] for cycle in cycles], [approx(20.0) for _ in cycles] + ) + self.assertEqual(sorted({cycle["pid"] for cycle in cycles}), [1, 2]) + # Compositing of one process never lands in the other one's phases. + for cycle in cycles: + self.assertEqual( + cycle["phases"]["compositing"], + approx(6.0 if cycle["pid"] == 1 else 3.0), + ) + + def test_a_cycle_spent_entirely_on_an_earlier_composition_is_analyzed(self): + # Compositing that overruns covers the whole of the next cycle, so that cycle + # holds no compositing mark of its own. Dropping it would leave the phase + # medians describing the fast frames only. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 6, 120), + mark("LayerTreeHostRenderingUpdate", 50, 55), + mark("LayerTreeHostRenderingUpdate", 100, 105), + mark("RenderLayerTree", 106, 115), + mark("LayerTreeHostRenderingUpdate", 150, 155), + ] + ) + + cycles = calculate_frame_cycles(data) + self.assertEqual( + cycles[1]["phases"], + { + "rendering_update": approx(5.0), + "waiting_for_compositing": approx(0.0), + "compositing": approx(45.0), + "idle": approx(0.0), + }, + ) + # The composition overran by 70 ms past the cycle it began in, and is counted + # there rather than once more in every cycle it runs through. + self.assertEqual(cycles[0]["composition_overrun"], approx(70.0)) + self.assertEqual(cycles[1]["composition_overrun"], approx(0.0)) + + def test_coverage_merges_the_cycles_of_two_processes(self): + marks = [] + for i in range(10): + for pid in (1, 2): + marks += [ + mark("LayerTreeHostRenderingUpdate", i * 100, i * 100 + 5, pid=pid), + mark("RenderLayerTree", i * 100 + 6, i * 100 + 50, pid=pid), + ] + + report = frame_cycle_report(sysprof_data(marks)) + # Both processes render throughout, so together they cover the 900 ms their + # cycles span, not the 1800 ms of summing them up. + self.assertEqual(report["cycles"], 18) + self.assertEqual(report["coverage"], approx(0.9)) + + def test_a_composition_begun_before_the_window_is_still_seen_in_it(self): + # The stall began at 6 ms, before the window, and holds up the cycle inside it. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 6, 120), + mark("LayerTreeHostRenderingUpdate", 50, 55), + mark("LayerTreeHostRenderingUpdate", 100, 105), + mark("RenderLayerTree", 106, 115), + mark("LayerTreeHostRenderingUpdate", 150, 155), + ] + ) + + windowed = calculate_frame_cycles(data, msec_to_nsec(40), None) + self.assertEqual( + [cycle["phases"] is not None for cycle in windowed], [True, True] + ) + self.assertEqual(windowed[0]["phases"]["compositing"], approx(45.0)) + + def test_a_stall_is_seen_past_a_shorter_mark_of_the_same_name(self): + # One process can emit two marks of one name that overlap, a long composition + # with a shorter one inside it, and the shorter one must not hide the stall. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 2), + mark("RenderLayerTree", 2, 200), + mark("RenderLayerTree", 10, 15), + mark("LayerTreeHostRenderingUpdate", 20, 22), + mark("LayerTreeHostRenderingUpdate", 40, 42), + ] + ) + + cycles = calculate_frame_cycles(data) + self.assertEqual( + [cycle["phases"] is not None for cycle in cycles], [True, True] + ) + self.assertEqual(cycles[1]["phases"]["compositing"], approx(18.0)) + + def test_a_capture_of_negative_length_reports_no_rate(self): + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 6, 10), + mark("LayerTreeHostRenderingUpdate", 20, 25), + ] + ) + + report = analyze._prepare_frame_cycle_report(data, 0, msec_to_nsec(-5), 16.67) + # A window of less than no length is no duration to be a share of. + self.assertIsNone(report["coverage"]) + + def test_no_cycles_says_so_rather_than_blaming_a_zero_median(self): + report = frame_cycle_report( + sysprof_data([mark("LayerTreeHostRenderingUpdate", 10, 15)]), + vblank_interval=16.67, + ) + self.assertEqual(report["cycles"], 0) + # A token, so that rewording the report cannot break a JSON consumer. + self.assertEqual(report["vblank_intervals_per_cycle_unknown"], "no_cycles") + self.assertTrue( + analyze.explanations.UNKNOWN_VBLANK_INTERVALS_PER_CYCLE["no_cycles"] + ) + + def test_every_overrunning_composition_of_a_stall_is_counted(self): + # Six 16 ms cycles, each compositing 9 ms past its own end. Counting only the + # first would report a sustained stall as a single slow frame. + marks = [] + for i in range(6): + marks += [ + mark("LayerTreeHostRenderingUpdate", i * 16, i * 16 + 3), + mark("RenderLayerTree", i * 16 + 5, i * 16 + 25), + ] + + cycles = calculate_frame_cycles(sysprof_data(marks)) + self.assertEqual( + [cycle["composition_overrun"] for cycle in cycles], + [approx(9.0) for _ in cycles], + ) + + def test_a_cycle_running_a_sliver_of_an_inherited_composition_is_measured(self): + # The composition of the first cycle ends a microsecond after the second one's + # rendering update does, so the second ran a sliver of it. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 2), + mark("RenderLayerTree", 3, 12.001), + mark("LayerTreeHostRenderingUpdate", 10, 12), + mark("LayerTreeHostRenderingUpdate", 30, 32), + ] + ) + + phases = calculate_frame_cycles(data)[1]["phases"] + self.assertEqual(phases["compositing"], approx(0.001)) + self.assertEqual(phases["idle"], approx(17.999)) + + def test_a_cycle_running_only_an_inherited_composition_is_measured(self): + # The composition began before the cycle and ran through the first half of its + # rendering update. Nothing began within the cycle, but something composited + # in it, so it is measured like a cycle whose own composition did the same. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 10, 100), + mark("LayerTreeHostRenderingUpdate", 50, 150), + mark("LayerTreeHostRenderingUpdate", 250, 255), + ] + ) + + phases = calculate_frame_cycles(data)[1]["phases"] + self.assertEqual(phases["rendering_update"], approx(100.0)) + self.assertEqual(phases["compositing"], approx(0.0)) + self.assertEqual(phases["idle"], approx(100.0)) + + def test_a_cycle_composited_beside_its_update_only_is_measured_as_zero(self): + # The same capture with the composition ending a microsecond earlier: the + # second cycle ran it only beside its own rendering update, so it composited + # for no time of its own. A microsecond of the phase separates the two cases, + # rather than one of them being measured and the other not. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 2), + mark("RenderLayerTree", 3, 11.999), + mark("LayerTreeHostRenderingUpdate", 10, 12), + mark("LayerTreeHostRenderingUpdate", 30, 32), + ] + ) + + phases = calculate_frame_cycles(data)[1]["phases"] + self.assertEqual(phases["compositing"], approx(0.0)) + self.assertEqual(phases["idle"], approx(18.0)) + + def test_a_mark_ending_before_the_capture_keeps_its_own_end(self): + # The parser shifts timestamps, so a mark that began before the capture ends + # at a negative time. Reporting it as ending at 0 would make it look like it + # was still running when the capture started. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", -20, -18), + mark("RenderLayerTree", -19, -17), + mark("LayerTreeHostRenderingUpdate", 0, 5), + mark("RenderLayerTree", 6, 12), + mark("LayerTreeHostRenderingUpdate", 20, 25), + ] + ) + + cycles = calculate_frame_cycles(data) + # The first cycle ends before the second begins, so the second waits for its + # own composition rather than inheriting one that was over long before. + self.assertEqual(cycles[1]["phases"]["waiting_for_compositing"], approx(1.0)) + + def test_a_gap_between_two_compositions_is_a_wait_not_compositing(self): + for inherited_end in [12.001, 11.999]: + with self.subTest(inherited_end=inherited_end): + # The cycle 10 -> 40 runs the tail of the composition it + # inherited, waits, and then composites again. Reading that as + # one span from the first mark to the last would report the wait + # as compositing, and a microsecond either side of the update + # would move 18 ms between the two phases. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 0, 2), + mark("RenderLayerTree", 3, inherited_end), + mark("LayerTreeHostRenderingUpdate", 10, 12), + mark("RenderLayerTree", 30, 38), + mark("LayerTreeHostRenderingUpdate", 40, 42), + ] + ) + + phases = calculate_frame_cycles(data)[1]["phases"] + self.assertEqual( + phases["waiting_for_compositing"], approx(18.0, abs=0.002) + ) + self.assertEqual(phases["compositing"], approx(8.0, abs=0.002)) + self.assertEqual(phases["idle"], approx(2.0)) + self.assertEqual(sum(phases.values()), approx(30.0)) + + def test_two_renderers_of_one_kind_are_told_apart_by_their_process(self): + # Two web processes rendering every 16 ms, 8 ms apart, both naming themselves + # "WebKit (Web)". Read by kind they would pair into 8 ms cycles and twice the + # frame rate of either. + marks = [] + for i in range(4): + marks += [ + mark("LayerTreeHostRenderingUpdate", i * 16, i * 16 + 2, pid=1), + mark("RenderLayerTree", i * 16 + 2, i * 16 + 9, pid=1), + mark("LayerTreeHostRenderingUpdate", i * 16 + 8, i * 16 + 10, pid=2), + mark("RenderLayerTree", i * 16 + 10, i * 16 + 15, pid=2), + ] + + report = frame_cycle_report(sysprof_data(marks, end_msec=200)) + self.assertEqual(report["processes"], 2) + self.assertEqual(report["duration_statistics"]["median"], approx(16.0)) + self.assertEqual(report["implied_fps"], approx(62.5)) + + def test_two_updates_at_one_instant_belong_to_two_processes(self): + # One process cannot open two updates at once, so a pair beginning together is + # two of them, and neither has a second update to close a cycle with. + data = sysprof_data( + [ + mark("LayerTreeHostRenderingUpdate", 10, 12, pid=1), + mark("LayerTreeHostRenderingUpdate", 10, 13, pid=2), + ] + ) + + self.assertEqual(calculate_frame_cycles(data), []) + + def test_a_report_without_a_rate_prints_no_rate(self): + # implied_fps is None wherever there is no median cycle to take it from, and + # the renderer must print that rather than formatting a number that is not one. + frame_cycle = { + "cycles": 1, + "processes": 1, + "duration_statistics": {"n": 1, "min": 0, "max": 0, "median": 0, "mean": 0}, + "resolved_duration_statistics": {}, + "phase_statistics": {phase: {} for phase in analyze.PHASES}, + "overrunning_composition_statistics": {}, + "implied_fps": None, + "coverage": None, + "capture_vblank_interval": 16.67, + "vblank_intervals_per_cycle": None, + "vblank_intervals_per_cycle_unknown": "zero_length_cycle", + } + + analyze._render_frame_cycle_numbers(frame_cycle) + + stdout = self.stdout() + self.assertIn("implied FPS (1000 / median cycle): -", stdout) + self.assertIn("cycles cover - of the analyzed duration", stdout) + + def test_a_steady_slow_capture_keeps_its_cycles(self): + # 20 FPS, so every cycle idles for 42 of its 50 ms. That is the rhythm of this + # capture, not the engine running out of work. + marks = [] + for i in range(20): + marks += [ + mark("LayerTreeHostRenderingUpdate", i * 50, i * 50 + 3), + mark("RenderLayerTree", i * 50 + 3, i * 50 + 8), + ] + + report = frame_cycle_report(sysprof_data(marks), vblank_interval=16.67) + self.assertEqual(report["cycles"], 19) + self.assertEqual(report["implied_fps"], approx(20.0)) + self.assertEqual(report["phase_statistics"]["idle"]["median"], approx(42.0)) + + def test_the_composition_overrun_median_describes_the_overrunning_cycles(self): + marks = [] + for i in range(10): + begin = i * 100 + # Two of the ten compositions run past the start of the next cycle. + compositing_end = begin + 150 if i in (3, 7) else begin + 50 + marks += [ + mark("LayerTreeHostRenderingUpdate", begin, begin + 10), + mark("RenderLayerTree", begin + 20, compositing_end), + ] + marks.append(mark("LayerTreeHostRenderingUpdate", 1000, 1010)) + + report = frame_cycle_report(sysprof_data(marks, end_msec=2000)) + self.assertEqual(report["overrunning_composition_statistics"]["n"], 2) + # The eight cycles that did not overrun must not median the number down to 0. + self.assertEqual( + report["overrunning_composition_statistics"]["median"], approx(50.0) + ) + + def test_an_unknown_refresh_rate_is_not_reported_as_zero(self): + report = { + "document": {"timespan": {"begin": 0.0, "end": 1000.0}}, + "rendering": { + "frames_rendered": 10, + "theoretical_fps": 10.0, + "vblanks": 1, + "vblank_interval_statistics": {}, + }, + } + + explanation = analyze._theoretical_fps_explanation(report) + # One mark is not none, and it is no reason to claim a 0 Hz display either. + self.assertIn("refreshed 1 time,", explanation) + self.assertNotIn("0.00 Hz", explanation) + + def test_a_zero_median_vblank_interval_is_not_reported_as_shared_timestamps(self): + report = { + "document": {"timespan": {"begin": 0.0, "end": 1000.0}}, + "rendering": { + "frames_rendered": 10, + "theoretical_fps": 10.0, + "vblanks": 4, + # Two of the four share a timestamp, which is enough for a zero median + # without the marks being the same instant. + "vblank_interval_statistics": {"median": 0.0}, + }, + } + + explanation = analyze._theoretical_fps_explanation(report) + self.assertIn("the median interval between two of the 4", explanation) + + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/helpers.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/helpers.py new file mode 100644 index 000000000000..1eaca3018129 --- /dev/null +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/helpers.py @@ -0,0 +1,76 @@ +"""What the tests build captures from, and read what a command printed with. + +A synthetic mark is shaped like what the parser produces: it carries no begin +time, so everything derives one from `end_time` and `duration`, and it names the +process that emitted it by pid as well as by kind. +""" + +import builtins +import unittest + +from webkitcorepy import OutputCapture + +from webkitsysprof.utils import msec_to_nsec + + +def mark(name, begin_msec, end_msec, group="WebKit (Web)", pid=1): + return { + "group": group, + "pid": pid, + "name": name, + "message": "", + "duration": msec_to_nsec(end_msec - begin_msec), + "end_time": msec_to_nsec(end_msec), + } + + +def sysprof_data(marks, begin_msec=0, end_msec=1000): + marks_by_name = {} + for a_mark in marks: + marks_by_name.setdefault(a_mark["name"], []).append(a_mark) + return { + "document": { + "timespan": { + "begin": msec_to_nsec(begin_msec), + "end": msec_to_nsec(end_msec), + } + }, + "marks": marks_by_name, + } + + +class approx: + """A number equal to another within a tolerance, relative unless `abs` says so. + + Durations are divided and summed before they are compared, so comparing them + exactly would test the rounding of binary floating point rather than the + analysis. Works inside a list or a dict too, since a float compared against one + of these defers to it. + """ + + def __init__(self, value, rel=1e-6, abs=None): + self.value = value + self.tolerance = abs if abs is not None else rel * max(builtins.abs(value), 1.0) + + def __eq__(self, other): + return builtins.abs(other - self.value) <= self.tolerance + + def __repr__(self): + return f"~{self.value!r}" + + +class SysprofTestCase(unittest.TestCase): + """A test reading back what the command under it printed. + + OutputCapture keeps stdout and the root logger to itself, so that a command + configuring logging does not outlive the test that ran it. + """ + + def setUp(self): + capture = OutputCapture() + capture.__enter__() + self.addCleanup(capture.__exit__, None, None, None) + self._capture = capture + + def stdout(self): + return self._capture.stdout.getvalue() diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py index 494c48de33bd..cee37270457e 100644 --- a/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py @@ -1,26 +1,30 @@ import argparse import json -import unittest from pathlib import Path -from webkitcorepy import OutputCapture +import unittest -from webkitsysprof import summary, dump, analyze +from .helpers import SysprofTestCase, approx, mark, sysprof_data -SAMPLE_CAPTURE_FILE = str(Path(__file__).parent / "assets" / "sample.syscap") +from webkitsysprof import summary, dump, analyze, histogram +from webkitsysprof.__main__ import main +from webkitsysprof.utils import ( + UsageError, + display_refreshes, + check_timespan_holds_data, + msec_to_nsec, + trim_marks_by_name_to_timespan, +) - -def _capture_stdout(func, args): - with OutputCapture() as captured: - func(args) - return captured.stdout.getvalue() +SAMPLE_CAPTURE_FILE = str(Path(__file__).parent / "assets" / "sample.syscap") -class SubcommandsTest(unittest.TestCase): +class SubcommandsTest(SysprofTestCase): def test_summary(self): args = argparse.Namespace(capture_file=SAMPLE_CAPTURE_FILE) - stdout = _capture_stdout(summary.summary, args) + summary.summary(args) + stdout = self.stdout() self.assertIn(f"File: {SAMPLE_CAPTURE_FILE}", stdout) self.assertIn("Marks: 669", stdout) self.assertIn("Counters: 53", stdout) @@ -29,105 +33,631 @@ def test_dump_marks_csv(self): args = argparse.Namespace( capture_file=SAMPLE_CAPTURE_FILE, marks=True, counters=False, format="csv" ) - stdout = _capture_stdout(dump.dump, args) + dump.dump(args) - stdout_lines = stdout.split("\n") + stdout_lines = self.stdout().split("\n") self.assertEqual(len(stdout_lines), 669 + 1 + 1) def test_dump_counters_csv(self): args = argparse.Namespace( capture_file=SAMPLE_CAPTURE_FILE, marks=False, counters=True, format="csv" ) - stdout = _capture_stdout(dump.dump, args) + dump.dump(args) - stdout_lines = stdout.split("\n") + stdout_lines = self.stdout().split("\n") self.assertEqual(len(stdout_lines), 540 + 1 + 1) def test_dump_marks_json(self): args = argparse.Namespace( capture_file=SAMPLE_CAPTURE_FILE, marks=True, counters=False, format="json" ) - stdout = _capture_stdout(dump.dump, args) + dump.dump(args) - marks = json.loads(stdout) + marks = json.loads(self.stdout()) self.assertEqual(len(marks), 669) self.assertEqual( set(marks[0].keys()), - {"group", "name", "message", "time", "duration", "end_time"}, + { + "group", + "pid", + "name", + "message", + "time", + "duration", + "end_time", + }, ) def test_dump_counters_json(self): args = argparse.Namespace( capture_file=SAMPLE_CAPTURE_FILE, marks=False, counters=True, format="json" ) - stdout = _capture_stdout(dump.dump, args) + dump.dump(args) - counter_values = json.loads(stdout) + counter_values = json.loads(self.stdout()) self.assertEqual(len(counter_values), 540) self.assertEqual( set(counter_values[0].keys()), - {"category", "name", "description", "time", "offset", "value"}, + { + "category", + "name", + "description", + "time", + "offset", + "value", + }, ) def test_analyze_text(self): args = argparse.Namespace( - capture_file=SAMPLE_CAPTURE_FILE, format="text", timespan="-" + capture_file=SAMPLE_CAPTURE_FILE, format="text", timespan="-", explain=False ) - stdout = _capture_stdout(analyze.analyze, args) + analyze.analyze(args) + stdout = self.stdout() self.assertIn("Timespan: 0.0000 - 4.4673 [s]", stdout) self.assertIn("vblanks: 35", stdout) + self.assertIn("Frame cycle:", stdout) + self.assertIn("- cycles: 2", stdout) + # The explanations are opt-in, so the report itself stays diffable. + self.assertNotIn( + "Theoretical FPS is the number of DidRenderFrame marks", stdout + ) + self.assertNotIn( + "A frame cycle starts when a LayerTreeHostRenderingUpdate", stdout + ) + + def test_analyze_text_with_explanations(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, format="text", timespan="-", explain=True + ) + analyze.analyze(args) + + stdout = self.stdout() + self.assertIn("Theoretical FPS is the number of DidRenderFrame marks", stdout) + self.assertIn( + "A frame cycle starts when a LayerTreeHostRenderingUpdate begins", stdout + ) + self.assertIn("StyleRecalc is an umbrella mark", stdout) + + def test_analyze_json_percentiles_stay_within_the_data_range(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-", explain=False + ) + analyze.analyze(args) + + report = json.loads(self.stdout()) + all_statistics = ( + [ + statistics + for mark in report["statistics"].values() + for statistics in mark.values() + ] + + [ + report["frame_cycle"]["duration_statistics"], + report["frame_cycle"]["resolved_duration_statistics"], + # The ones the text report prints percentiles of inline. + report["rendering"]["vblank_interval_statistics"], + report["rendering"]["vblanks_per_rendering_update"]["statistics"], + report["rendering"]["frame_compositions_per_vblank_statistics"], + ] + + list(report["frame_cycle"]["phase_statistics"].values()) + ) + + # Extrapolating past the samples used to yield a P25 below the minimum, a P99 + # above the maximum and negative durations. + for statistics in all_statistics: + for percentile in statistics.get("percentiles", {}).values(): + self.assertTrue(statistics["min"] <= percentile <= statistics["max"]) def test_analyze_json(self): args = argparse.Namespace( - capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-" + capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-", explain=False ) - stdout = _capture_stdout(analyze.analyze, args) + analyze.analyze(args) + stdout = self.stdout() report = json.loads(stdout) self.assertEqual(int(report["document"]["timespan"]["begin"]), 0) self.assertEqual(int(report["document"]["timespan"]["end"]), 4467) self.assertEqual(report["rendering"]["vblanks"], 35) + self.assertEqual(report["frame_cycle"]["cycles"], 2) + self.assertEqual(report["frame_cycle"]["resolved_duration_statistics"]["n"], 2) + self.assertEqual( + set(report["frame_cycle"]["phase_statistics"]), + { + "rendering_update", + "waiting_for_compositing", + "compositing", + "idle", + }, + ) + + def test_analyze_json_frame_cycle_phases_add_up_to_cycle_duration(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-", explain=False + ) + analyze.analyze(args) + + frame_cycle = json.loads(self.stdout())["frame_cycle"] + phases_mean = sum( + statistics["mean"] + for statistics in frame_cycle["phase_statistics"].values() + ) + # Against the cycles the phases came from, not all of them: the two differ as + # soon as one cycle cannot be split into phases. + self.assertEqual( + phases_mean, + approx(frame_cycle["resolved_duration_statistics"]["mean"], rel=1e-6), + ) def test_analyze_json_statistics_cover_all_relevant_marks(self): args = argparse.Namespace( - capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-" + capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-", explain=False ) - stdout = _capture_stdout(analyze.analyze, args) + analyze.analyze(args) - statistics = json.loads(stdout)["statistics"] + statistics = json.loads(self.stdout())["statistics"] self.assertEqual(set(statistics), set(analyze.MARKS_RELEVANT_FOR_STATISTICS)) self.assertEqual(statistics["CompositingUpdate"]["duration"]["n"], 7) self.assertEqual(statistics["RenderTreeBuild"]["duration"]["n"], 3) - # Tile geometry depends on the rendering backend, so only check that - # the dirty area was extracted from every PaintTile message. + # Tile geometry depends on the rendering backend, so only check that the + # dirty area was extracted from every PaintTile message. self.assertEqual(statistics["PaintTile"]["dirty_pixels"]["n"], 80) self.assertGreater(statistics["PaintTile"]["dirty_pixels"]["min"], 0) + def test_analyze_with_a_timespan_holding_vblanks_but_no_rendering_update(self): + # The window has vblanks, so the no-vblanks early return does not save it, and + # trimming drops the first rendering update for reaching past the window end. + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, + format="text", + timespan="0-370", + explain=False, + ) + analyze.analyze(args) + + stdout = self.stdout() + self.assertIn("- cycles: 0", stdout) + self.assertIn("no two consecutive LayerTreeHostRenderingUpdate marks", stdout) + + def test_analyze_json_leaves_out_a_cycle_reaching_past_the_timespan(self): + # The only cycle of this window begins at 371.6 ms and ends at 524.2 ms, so it + # is no frame of the window and must not be timed as one. + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, + format="json", + timespan="0-400", + explain=False, + ) + analyze.analyze(args) + + frame_cycle = json.loads(self.stdout())["frame_cycle"] + self.assertEqual(frame_cycle["cycles"], 0) + self.assertEqual(frame_cycle["coverage"], approx(0.0)) + + def test_analyze_resolves_a_cycle_whose_compositing_marks_trimming_drops(self): + # The cycle 524.2 -> 560.8 ms lies within this window, but composites in + # RenderLayerTree 549.8 -> 571.1, which reaches past the window end and is + # trimmed away. Reconstructing from the untrimmed capture keeps it. + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, + format="json", + timespan="370-561", + explain=False, + ) + analyze.analyze(args) + + frame_cycle = json.loads(self.stdout())["frame_cycle"] + self.assertEqual(frame_cycle["cycles"], 2) + self.assertEqual(frame_cycle["resolved_duration_statistics"]["n"], 2) + # Without that mark the composition looks as if it ended with PaintToGLContext + # at 560.6 ms, just inside its cycle, so the overrun would go unnoticed. + self.assertEqual(frame_cycle["overrunning_composition_statistics"]["n"], 2) + + def test_explain_is_rejected_for_the_json_format_by_the_module_api(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-", explain=True + ) + with self.assertRaises(UsageError): + analyze.analyze(args) + + def test_analyze_rejects_a_timespan_it_cannot_honour(self): + # Silently analyzing the whole capture, or a window the argument never asked + # for, reads as a result for the requested window. + for timespan in [ + "abc", + "0-1e3", + "1-2-3", + "5000-", + "5000-6000", + "", + # Of no length, so it encloses nothing to analyze. + "0-0", + "500-500", + ]: + with self.subTest(timespan=timespan): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, + format="text", + timespan=timespan, + explain=False, + ) + with self.assertRaises(UsageError): + analyze.analyze(args) + + with self.assertRaises(SystemExit): + main(["analyze", "-t", timespan, SAMPLE_CAPTURE_FILE]) + + def test_analyze_clamps_a_timespan_reaching_past_the_capture(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, + format="json", + timespan="0-100000", + explain=False, + ) + analyze.analyze(args) + + report = json.loads(self.stdout()) + # The rates divide by the analyzed duration, so a window stretching past the + # capture would water every one of them down. + self.assertEqual(int(report["document"]["timespan"]["end"]), 4467) + self.assertEqual( + report["rendering"]["theoretical_fps"], approx(0.448, abs=0.001) + ) + + def test_vblanks_per_rendering_update_does_not_depend_on_the_mark_order(self): + # The walk needs the updates in begin order, which the grouping does not grant. + parsed = analyze.parse(SAMPLE_CAPTURE_FILE, marks=True, counters=False) + data = analyze.sysprof_data_with_marks_by_name(parsed) + in_parse_order = analyze._prepare_rendering_report( + data, display_refreshes(data) + ) + + for marks in data["marks"].values(): + marks.reverse() + self.assertEqual( + analyze._prepare_rendering_report( + data, display_refreshes(data) + )["vblanks_per_rendering_update"], + in_parse_order["vblanks_per_rendering_update"], + ) + self.assertEqual( + analyze._prepare_rendering_report( + data, display_refreshes(data) + )["vblank_interval_statistics"], + in_parse_order["vblank_interval_statistics"], + ) + + def test_a_capture_of_no_length_reports_no_rate(self): + # A window of no length is rejected, but a capture of no length is the + # capture's own doing and still has to be reported on. + data = sysprof_data( + [mark("DidRenderFrame", 0, 0)], begin_msec=0, end_msec=0 + ) + + report = analyze._prepare_report(data, data) + + frame_cycle = report["frame_cycle"] + # No cycle and no duration to divide, rather than a cycle of zero length that + # fits in zero vblank intervals and covers 0% of nothing. + self.assertEqual(frame_cycle["cycles"], 0) + self.assertIsNone(frame_cycle["implied_fps"]) + self.assertIsNone(frame_cycle["vblank_intervals_per_cycle"]) + self.assertIsNone(frame_cycle["coverage"]) + # No duration to divide frames by either. + self.assertIsNone(report["rendering"]["theoretical_fps"]) + + def test_delta_histogram_deltas_come_from_the_requested_mark(self): + parsed = histogram.parse(SAMPLE_CAPTURE_FILE, marks=True, counters=False) + data = histogram.sysprof_data_with_marks_by_name(parsed) + + deltas = histogram.intervals_between_marks( + histogram.marks_in_time_order(data, "DisplayLinkUpdate") + ) + self.assertEqual(len(deltas), 34) + self.assertGreater(min(deltas), 0) + self.assertEqual( + histogram.intervals_between_marks( + histogram.marks_in_time_order(data, "NoSuchMark") + ), + [], + ) + self.assertTrue(10 <= histogram._calculate_optimal_bins(deltas) <= 100) + + def test_delta_histogram_honours_the_timespan(self): + parsed = histogram.parse(SAMPLE_CAPTURE_FILE, marks=True, counters=False) + data = histogram.trim_marks_by_name_to_timespan( + histogram.sysprof_data_with_marks_by_name(parsed), None, msec_to_nsec(500) + ) + + self.assertEqual(len(data["marks"]["DisplayLinkUpdate"]), 10) + self.assertEqual( + len( + histogram.intervals_between_marks( + histogram.marks_in_time_order(data, "DisplayLinkUpdate") + ) + ), + 9, + ) + + def test_analyze_json_counts_every_vblank_interval_of_the_timespan(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-", explain=False + ) + analyze.analyze(args) + + rendering = json.loads(self.stdout())["rendering"] + # 35 vblanks are 34 intervals, and the ones after the last composition are + # samples too. Counting only up to it used to report 15 and a mean twice as + # high as the capture actually composited. + self.assertEqual(rendering["vblanks"], 35) + self.assertEqual(rendering["frame_compositions_per_vblank_statistics"]["n"], 34) + self.assertEqual( + rendering["frame_compositions_per_vblank_statistics"]["mean"], + approx(2 / 34), + ) + + def test_frame_compositions_outside_the_vblank_range_are_left_out(self): + vblanks = [mark("DisplayLinkUpdate", msec, msec) for msec in (0, 16, 32)] + compositions = [ + mark("DidRenderFrame", msec, msec) for msec in (5, 40, 45, 50, 55) + ] + + # The four compositions after the last vblank belong to no interval between + # two refreshes. Booking them into the last one made up a burst that the + # capture never had. + self.assertEqual( + analyze._calculate_frame_compositions_per_vblank(vblanks, compositions), + [ + 1, + 0, + ], + ) + + def test_frame_rendering_reasons_bucket_a_frame_that_named_none(self): + def did_render_frame(message, msec=0): + return { + "name": "DidRenderFrame", + "message": message, + "duration": 0, + "end_time": msec_to_nsec(msec), + } + + data = { + "document": {"timespan": {"begin": 0, "end": msec_to_nsec(1000)}}, + "marks": { + "DidRenderFrame": [ + did_render_frame("reasons: Scrolling"), + did_render_frame("reasons: Scrolling, AsyncScrolling", 1), + did_render_frame("reasons: ", 2), + did_render_frame("reasons: ", 3), + ] + }, + } + + # The reasons of a frame are one bucket, however many it names, and the + # frames that named none share one of their own. + self.assertEqual( + analyze._prepare_rendering_report(data, display_refreshes(data))[ + "frame_rendering_reasons" + ], + {"Scrolling": 1, "Scrolling, AsyncScrolling": 1, "_none": 2}, + ) + + def test_explain_reaches_the_report_through_the_command_line(self): + # Through main(), so that renaming the flag or its dest cannot quietly stop the + # explanations from being printed. + main(["analyze", "-e", SAMPLE_CAPTURE_FILE]) + + stdout = self.stdout() + self.assertIn( + "A frame cycle starts when a LayerTreeHostRenderingUpdate begins", stdout + ) + self.assertIn("StyleRecalc is an umbrella mark", stdout) + + def test_a_capture_of_its_own_broken_timespan_is_no_usage_error(self): + # No -t was passed, so nothing the user typed can be at fault. + data = {"document": {"timespan": {"begin": 0, "end": -5}}, "marks": {}} + self.assertEqual( + trim_marks_by_name_to_timespan(data, None, None)["document"]["timespan"][ + "end" + ], + -5, + ) + + def test_statistics_are_matched_by_wording_rather_than_word_position(self): + extract = analyze.STATISTICAL_DATA_EXTRACTORS["UpdateTiles"] + + self.assertEqual( + extract({"message": "dirty tiles: 40", "duration": 0})["tiles"], 40 + ) + # A mark that carries no message at all leaves the statistic out, rather + # than reporting a number read from somewhere else. + self.assertIsNone(extract({"message": "", "duration": 0})["tiles"]) + + def test_explaining_an_empty_frame_cycle_section_still_explains_it(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, + format="text", + timespan="0-370", + explain=True, + ) + analyze.analyze(args) + + stdout = self.stdout() + self.assertIn("- cycles: 0", stdout) + # An empty section is what raises the question the explanation answers. + self.assertIn( + "A frame cycle starts when a LayerTreeHostRenderingUpdate begins", stdout + ) + + def test_a_broken_capture_is_no_usage_error_even_with_a_timespan(self): + # -t 0- asks for exactly the capture's own range, so nothing the user typed + # can be at fault when that range runs backwards or is of no length. + check_timespan_holds_data(0, msec_to_nsec(-5), 0, None) + check_timespan_holds_data(0, msec_to_nsec(-5), None, None) + check_timespan_holds_data(0, 0, 0, msec_to_nsec(5)) + + def test_a_window_meeting_the_capture_at_one_point_is_rejected(self): + capture = (msec_to_nsec(100), msec_to_nsec(200)) + # Clamped to the capture, either of these encloses a single instant, which + # is no duration to divide by and no range for a mark to fall inside. + with self.assertRaises(UsageError): + check_timespan_holds_data(*capture, msec_to_nsec(200), None) + with self.assertRaises(UsageError): + check_timespan_holds_data(*capture, None, msec_to_nsec(100)) + # One millisecond of overlap is still a window. + check_timespan_holds_data(*capture, msec_to_nsec(199), None) + check_timespan_holds_data(*capture, None, msec_to_nsec(101)) + + def test_json_percentiles_keep_the_fiftieth(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, format="json", timespan="-", explain=False + ) + analyze.analyze(args) + + percentiles = json.loads(self.stdout())["statistics"]["StyleRecalc"][ + "duration" + ]["percentiles"] + # Read by consumers since before the frame cycle section existed. + self.assertEqual(sorted(percentiles), ["25", "50", "75", "99"]) + + def test_delta_histogram_keeps_the_processes_apart(self): + # Two processes rendering at 16 ms, offset by 8 ms from one another. + marks = [] + for i in range(5): + marks.append( + mark("LayerTreeHostRenderingUpdate", i * 16, i * 16 + 2, pid=1) + ) + marks.append( + mark("LayerTreeHostRenderingUpdate", i * 16 + 8, i * 16 + 10, pid=2) + ) + data = sysprof_data(marks, end_msec=100) + + # Merged, the deltas would read 8 ms and the histogram would peak at half the + # frame interval of either process. + deltas = histogram._delta_times_ms(data, "LayerTreeHostRenderingUpdate") + self.assertEqual(deltas, [approx(16.0)] * 8) + + def test_composition_overrun_says_nothing_where_nothing_was_analyzed(self): + frame_cycle = { + "cycles": 9, + "processes": 1, + "duration_statistics": {"n": 9, "min": 1, "max": 1, "median": 1, "mean": 1}, + "resolved_duration_statistics": {}, + "phase_statistics": {phase: {} for phase in analyze.PHASES}, + "overrunning_composition_statistics": {}, + "implied_fps": 1.0, + "coverage": 0.5, + "capture_vblank_interval": 16.0, + "vblank_intervals_per_cycle": 1.0, + "vblank_intervals_per_cycle_unknown": None, + } + + analyze._render_frame_cycle_numbers(frame_cycle) + + stdout = self.stdout() + # "in none of 0 analyzed cycles" would read as a measurement never made. + self.assertIn("- composition overrunning the cycle: -", stdout) + # The note explains shares, and every phase here reads as -. + self.assertNotIn("need not total 100%", stdout) + + def test_an_update_ending_on_the_first_refresh_spans_it(self): + vblanks = [mark("DisplayLinkUpdate", msec, msec) for msec in (100, 116, 132)] + update = [mark("LayerTreeHostRenderingUpdate", 90, 100)] + + # It ran up to that refresh, so it spanned one, and both ends of the range + # are read the same way. + self.assertEqual( + analyze._calculate_vblanks_per_rendering_update(vblanks, update), [1] + ) + + def test_refreshes_that_composited_nothing_are_samples_of_nothing(self): + vblanks = [ + mark("DisplayLinkUpdate", i * 16, i * 16, "WebKit (UI)", pid=1) + for i in range(60) + ] + data = sysprof_data(vblanks, end_msec=1000) + + rendering = analyze._prepare_rendering_report(data, vblanks) + + # A capture that composited nothing is not a capture without refreshes: every + # interval held no composition, which is 59 samples of zero. + statistics = rendering["frame_compositions_per_vblank_statistics"] + self.assertEqual(statistics["n"], 59) + self.assertEqual(statistics["max"], 0) + + def test_dump_csv_carries_every_column_of_a_row(self): + args = argparse.Namespace( + capture_file=SAMPLE_CAPTURE_FILE, marks=True, counters=False, format="csv" + ) + dump.dump(args) + + header = self.stdout().splitlines()[0] + self.assertEqual(header, "group;pid;name;message;time;duration;end_time") + + def test_a_window_without_refreshes_keeps_the_capture_interval(self): + # The link stops before the window begins. Its interval is a property of the + # display, so the cycles of the window are still measured in it. + marks = [ + mark("DisplayLinkUpdate", i * 16, i * 16, "WebKit (UI)", pid=1) + for i in range(30) + ] + marks += [ + mark("LayerTreeHostRenderingUpdate", 600 + i * 20, 600 + i * 20 + 5, pid=2) + for i in range(10) + ] + parsed = {"document": {"timespan": [0, msec_to_nsec(1000)]}, "marks": marks} + untrimmed = analyze.sysprof_data_with_marks_by_name(parsed) + window = analyze.trim_marks_by_name_to_timespan( + untrimmed, msec_to_nsec(600), msec_to_nsec(800) + ) + + report = analyze._prepare_report(window, untrimmed) + + self.assertEqual(report["rendering"]["vblanks"], 0) + self.assertEqual(report["frame_cycle"]["capture_vblank_interval"], approx(16.0)) + self.assertEqual( + report["frame_cycle"]["vblank_intervals_per_cycle"], approx(20 / 16) + ) + def test_analyze_with_custom_timespan(self): args = argparse.Namespace( - capture_file=SAMPLE_CAPTURE_FILE, format="text", timespan="0-0" + capture_file=SAMPLE_CAPTURE_FILE, + format="text", + timespan="0-370", + explain=False, ) - stdout = _capture_stdout(analyze.analyze, args) + analyze.analyze(args) - self.assertIn("Timespan: 0.0000 - 0.0000 [s]", stdout) - self.assertIn("vblanks: 0", stdout) + stdout = self.stdout() + self.assertIn("Timespan: 0.0000 - 0.3700 [s]", stdout) + self.assertIn("vblanks: 2", stdout) def test_analyze_with_custom_timespan_begin(self): args = argparse.Namespace( - capture_file=SAMPLE_CAPTURE_FILE, format="text", timespan="500-" + capture_file=SAMPLE_CAPTURE_FILE, + format="text", + timespan="500-", + explain=False, ) - stdout = _capture_stdout(analyze.analyze, args) + analyze.analyze(args) + stdout = self.stdout() self.assertIn("Timespan: 0.5000 - 4.4673 [s]", stdout) self.assertIn("vblanks: 25", stdout) def test_analyze_with_custom_timespan_end(self): args = argparse.Namespace( - capture_file=SAMPLE_CAPTURE_FILE, format="text", timespan="-500" + capture_file=SAMPLE_CAPTURE_FILE, + format="text", + timespan="-500", + explain=False, ) - stdout = _capture_stdout(analyze.analyze, args) + analyze.analyze(args) + stdout = self.stdout() self.assertIn("Timespan: 0.0000 - 0.5000 [s]", stdout) self.assertIn("vblanks: 10", stdout) diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py new file mode 100644 index 000000000000..013a0da2b20f --- /dev/null +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py @@ -0,0 +1,92 @@ +"""The helpers webkitsysprof.utils holds, which every command reads marks with.""" + +import unittest + +from .helpers import SysprofTestCase, approx, mark, sysprof_data + +from webkitsysprof.utils import ( + UsageError, + marks_in_time_order, + merged_spans, + msec_to_nsec, + parse_timespan_argument, + percentiles, + sysprof_data_with_marks_by_name, + intervals_between_marks, + median_vblank_interval, + display_refreshes, +) + + +class UtilsTest(SysprofTestCase): + def test_spans_are_merged_where_they_overlap_or_touch(self): + self.assertEqual( + merged_spans([(0, 5), (3, 8), (8, 9), (20, 25)]), [(0, 9), (20, 25)] + ) + # An empty span is no span at all, rather than one of no length in between. + self.assertEqual(merged_spans([(5, 5), (10, 12)]), [(10, 12)]) + self.assertEqual(merged_spans([]), []) + + def test_a_timespan_bound_is_digits_and_nothing_else(self): + # int() would take every one of these and analyze a window nobody asked for. + for timespan in [ + " 5", + "+5", + "0-1_000", + "5\n", + "500-100", + # Of no length, so it encloses nothing between its bounds. + "0-0", + "500-500", + "abc", + "0-1e3", + "1-2-3", + "", + ]: + with self.subTest(timespan=timespan), self.assertRaises(UsageError): + parse_timespan_argument(timespan) + + def test_percentiles_outside_the_range_of_the_quantiles_are_rejected(self): + # Percentile 0 used to index the list from the back and report P99 as P0. + for percentile in [0, 100, -1]: + with self.subTest(percentile=percentile), self.assertRaises(ValueError): + percentiles([1.0, 2.0, 3.0], [percentile]) + + def test_a_bare_bound_is_the_begin_of_the_timespan(self): + self.assertEqual(parse_timespan_argument("500"), (msec_to_nsec(500), None)) + + def test_vblank_intervals_do_not_depend_on_the_order_of_the_marks(self): + data = sysprof_data( + [ + mark("DisplayLinkUpdate", 32, 33), + mark("DisplayLinkUpdate", 0, 1), + mark("DisplayLinkUpdate", 16, 17), + ] + ) + + self.assertEqual( + intervals_between_marks(marks_in_time_order(data, "DisplayLinkUpdate")), + [ + approx(16.0), + approx(16.0), + ], + ) + self.assertEqual( + median_vblank_interval(display_refreshes(data)), + approx(16.0), + ) + + def test_reshaping_the_same_parsed_data_twice_yields_the_same_result(self): + parsed_data = { + "document": {"timespan": [0, msec_to_nsec(1000)]}, + "marks": [mark("LayerTreeHostRenderingUpdate", 0, 5)], + } + + first = sysprof_data_with_marks_by_name(parsed_data) + second = sysprof_data_with_marks_by_name(parsed_data) + self.assertEqual(first["document"]["timespan"], second["document"]["timespan"]) + self.assertEqual(parsed_data["document"]["timespan"], [0, msec_to_nsec(1000)]) + + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py b/Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py index 50960215cf25..f0a8791006bf 100644 --- a/Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py +++ b/Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py @@ -1,4 +1,18 @@ -from typing import Any, Dict, Optional, Tuple, Union +import collections +import logging +import re +import statistics +from typing import ( + Any, + Dict, + Iterable, + List, + Literal, + Optional, + Sequence, + Tuple, + Union, +) NSEC_PER_MSEC = 1_000_000 NSEC_PER_SEC = 1_000_000_000 @@ -21,20 +35,56 @@ def msec_to_nsec(msec: int) -> int: return msec * NSEC_PER_MSEC +# A timespan bound is a plain number of milliseconds, nothing int() would also take. +# Ended with \Z rather than $, which would let a trailing newline through. +MILLISECONDS_RE = re.compile(r"^\d+\Z") + + +class UsageError(ValueError): + """The user asked for something the tool cannot do, e.g. a backwards timespan. + + Told apart from the other ValueErrors so that the command line can report it as + a usage error while a broken capture still raises where it broke. + """ + + +def mark_begin(mark: Dict[str, Any]) -> int: + """Marks carry an end time and a duration, so a begin time has to be derived.""" + return int(mark["end_time"] - mark["duration"]) + + def parse_timespan_argument(arg: str) -> Tuple[Optional[int], Optional[int]]: - timespan_begin, timespan_end = None, None + """Parse "-" in milliseconds, either side of which may be empty. + + A single bound without a dash is the begin, e.g. "500" is "from 500 ms on". + """ + + def parse_bound(value: str) -> Optional[int]: + if value == "": + return None + # Digits and nothing else: int() would take " 5", "+5" and "1_000" too, and + # a timespan the tool reads as something other than what was typed is what + # rejecting a malformed one is meant to prevent. + if not MILLISECONDS_RE.match(value): + raise UsageError(f"timespan bound is no number of milliseconds: {value}") + return int(value) + + if arg == "": + raise UsageError("timespan is empty") values = arg.split("-") + if len(values) > 2: + raise UsageError(f'timespan is no "-" in milliseconds: {arg}') + timespan_begin = parse_bound(values[0]) + timespan_end = parse_bound(values[1]) if len(values) == 2 else None - if len(values) > 0: - try: - timespan_begin = int(values[0]) - except ValueError: - pass - if len(values) > 1: - try: - timespan_end = int(values[1]) - except ValueError: - pass + if timespan_begin is not None and timespan_end is not None: + if timespan_begin > timespan_end: + raise UsageError(f"timespan begins after it ends: {arg}") + # A window of no length encloses no data and is no duration to divide by, + # so every rate taken over it is unknown. Rejected rather than reported as + # a row of zeroes, which reads as a measurement that was made. + if timespan_begin == timespan_end: + raise UsageError(f"timespan is of no length: {arg}") return ( msec_to_nsec(timespan_begin) if timespan_begin is not None else None, @@ -42,24 +92,212 @@ def parse_timespan_argument(arg: str) -> Tuple[Optional[int], Optional[int]]: ) -def trim_sysprof_data_to_timespan( +def sysprof_data_with_marks_by_name(sysprof_data: Dict[str, Any]) -> Dict[str, Any]: + """Reshape parsed data so that marks are grouped by name. + + The groups keep the order the marks were parsed in, so the callers that walk + them in time order sort them the way they need. The input is left untouched. + """ + document = dict(sysprof_data["document"]) + document["timespan"] = { + "begin": sysprof_data["document"]["timespan"][0], + "end": sysprof_data["document"]["timespan"][1], + } + marks_by_name: Dict[str, List[Dict[str, Any]]] = collections.defaultdict(list) + for mark in sysprof_data["marks"]: + marks_by_name[mark["name"]].append(mark) + # Handed out as a plain dict, so that a name the capture never held is a + # KeyError for the callers that subscript it rather than an empty list. + return {"document": document, "marks": dict(marks_by_name)} + + +def check_timespan_holds_data( + capture_begin: int, + capture_end: int, + timespan_begin: Optional[int], + timespan_end: Optional[int], +) -> None: + """Reject a timespan the caller asked for that the capture cannot answer.""" + if capture_begin >= capture_end: + # The capture holds no range for a window to miss, so there is nothing the + # caller could have asked for that would be right, and blaming the window + # for what the capture lacks would point at the wrong thing. Say so, so + # that the empty report below carries its reason with it. + logging.warning( + "The capture spans no time (begins at %d, ends at %d), so the report " + "has no rate to give.", + capture_begin, + capture_end, + ) + return + if timespan_begin is not None and timespan_begin >= capture_end: + raise UsageError( + "timespan begins at or after the capture ends, so it holds no data" + ) + if timespan_end is not None and timespan_end <= capture_begin: + raise UsageError( + "timespan ends at or before the capture begins, so it holds no data" + ) + + +def trim_marks_by_name_to_timespan( sysprof_data: Dict[str, Any], timespan_begin: Optional[int], timespan_end: Optional[int], ) -> Dict[str, Any]: - trimmed_document = sysprof_data["document"] + """Narrow data grouped by sysprof_data_with_marks_by_name() to a timespan. + Marks that cross a boundary are dropped, since only part of them happened + within the window. The input is left untouched, so its callers keep the + untrimmed data, which the frame cycles need. + """ + # Narrowed even where no bound was given, since the capture holds marks that + # begin before its own timespan does, and the frame cycles are cut to that + # timespan either way. + timespan = dict(sysprof_data["document"]["timespan"]) + # Clamped to the capture, so that a window reaching past it does not stretch + # the analyzed duration that every rate is divided by. if timespan_begin is not None: - trimmed_document["timespan"][0] = timespan_begin + timespan["begin"] = max(timespan_begin, timespan["begin"]) if timespan_end is not None: - trimmed_document["timespan"][1] = timespan_end + timespan["end"] = min(timespan_end, timespan["end"]) def mark_in_timespan(mark: Dict[str, Any]) -> bool: return ( - (mark["end_time"] - mark["duration"]) >= trimmed_document["timespan"][0] - ) and mark["end_time"] <= trimmed_document["timespan"][1] + mark_begin(mark) >= timespan["begin"] + and mark["end_time"] <= timespan["end"] + ) + trimmed_document = dict(sysprof_data["document"]) + trimmed_document["timespan"] = timespan return { "document": trimmed_document, - "marks": [mark for mark in sysprof_data["marks"] if mark_in_timespan(mark)], + "marks": { + mark_name: [mark for mark in marks if mark_in_timespan(mark)] + for mark_name, marks in sysprof_data["marks"].items() + }, } + + +def merged_spans(spans: Iterable[Tuple[int, int]]) -> List[Tuple[int, int]]: + """The [begin, end) spans merged where they overlap or touch, in order. + + Empty spans are dropped, so that a mark clipped to nothing does not become a + span of no length between two real ones. + """ + merged: List[Tuple[int, int]] = [] + for begin, end in sorted(spans): + if end <= begin: + continue + if merged and begin <= merged[-1][1]: + merged[-1] = (merged[-1][0], max(merged[-1][1], end)) + else: + merged.append((begin, end)) + return merged + + +def sample_statistics( + values: Sequence[Union[int, float]], wanted: Sequence[int] = () +) -> Dict[str, Any]: + if not values: + return {} + return { + "n": len(values), + "min": min(values), + "max": max(values), + "mean": statistics.mean(values), + "stddev": statistics.stdev(values) if len(values) > 1 else 0, + # Both of these sort what they are given, so handing them a sorted sequence + # would add a sort rather than save the two they do. + "median": statistics.median(values), + "percentiles": percentiles(values, wanted), + } + + +def percentiles( + values: Sequence[Union[int, float]], + wanted: Sequence[int], + method: Literal["inclusive", "exclusive"] = "inclusive", +) -> Dict[str, float]: + """Percentiles interpolated between the data points, empty below two of them. + + Keyed by the percentile as a string, the shape json.dumps() turns an int key + into anyway, so that a report read back from JSON is the report written. + + The inclusive method stays between the smallest and the largest sample, while + the exclusive one extrapolates past them: on few samples it reports a P25 below + the minimum, a P99 above the maximum and negative durations. Reporting the + samples wants the former, estimating the spread of what they were drawn from + wants the latter. Both need two data points, and below that quantiles() raises + before Python 3.13. + """ + for percentile in wanted: + if not 1 <= percentile <= 99: + raise ValueError(f"no percentile between 1 and 99: {percentile}") + # Before the sort quantiles() does, which nothing would read. + if not wanted: + return {} + if len(values) < 2: + return {} + quantiles: List[float] = statistics.quantiles(values, n=100, method=method) + return {str(percentile): quantiles[percentile - 1] for percentile in wanted} + + +def mark_pid(mark: Dict[str, Any]) -> int: + """The process a mark was emitted by, as the capture records it.""" + return int(mark.get("pid", 0)) + + +def marks_by_process( + marks: Sequence[Dict[str, Any]], +) -> Dict[int, List[Dict[str, Any]]]: + """The marks grouped by the process they came from, in process order.""" + by_pid: Dict[int, List[Dict[str, Any]]] = collections.defaultdict(list) + for mark in marks: + by_pid[mark_pid(mark)].append(mark) + return dict(sorted(by_pid.items())) + + +def marks_in_time_order( + sysprof_data: Dict[str, Any], mark_name: str +) -> List[Dict[str, Any]]: + """The marks of that name, ordered by when they ended. + + The grouping keeps the marks in parse order, so every walk that depends on + time order sorts them here. + """ + return sorted( + sysprof_data["marks"].get(mark_name, []), key=lambda mark: mark["end_time"] + ) + + +def intervals_between_marks(marks: Sequence[Dict[str, Any]]) -> List[float]: + """Times between the ends of consecutive marks, in milliseconds. + + Takes the marks in time order, as marks_in_time_order() returns them. + """ + return [ + nsec_to_msec(marks[i]["end_time"] - marks[i - 1]["end_time"]) + for i in range(1, len(marks)) + ] + + +def display_refreshes(sysprof_data: Dict[str, Any]) -> List[Dict[str, Any]]: + """The DisplayLinkUpdate marks of the capture, in time order. + + All of them: the display links all live in the UI process, one per display, so + the marks of a capture are the refreshes of one process either way. Two displays + driven at once are merged, which no mark tells apart, and so is a link that + stopped and started again: the gap it leaves counts as one very long interval + between two refreshes. + """ + return marks_in_time_order(sysprof_data, "DisplayLinkUpdate") + + +def median_vblank_interval(refreshes: Sequence[Dict[str, Any]]) -> Optional[float]: + """Median time between display refreshes in milliseconds, None if unknown. + + Takes the refreshes as display_refreshes() returns them. + """ + intervals = intervals_between_marks(refreshes) + return statistics.median(intervals) if intervals else None From 70826d7c1dcbf58ac38c6e4d7378e5a1806b5052 Mon Sep 17 00:00:00 2001 From: Chris Dumez Date: Fri, 28 Aug 2026 01:45:25 -0700 Subject: [PATCH 022/103] WebEvent and its subclasses should always be heap-allocated https://bugs.webkit.org/show_bug.cgi?id=322699 Reviewed by Ryosuke Niwa. WebEvent subclassed CanMakeThreadSafeCheckedPtr with CheckedPtrDeleteCheckException::Yes, because CanMakeCheckedPtr only works as intended for heap-allocated objects: WebEvent and its subclasses were routinely stack-allocated, so operator delete never ran, setDidBeginCheckedPtrDeletion() was never called, and the delete check in ~CanMakeCheckedPtrBase() could not hold. Make the hierarchy always heap-allocated instead. WebEvent now derives from ThreadSafeRefCounted and each concrete class is created through a static create() returning Ref<>. CanMakeThreadSafeCheckedPtr, WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR and the CheckedPtrDeleteCheckException::Yes exception are all removed from WebEvent. Refcounting is thread-safe because EventDispatcher handles wheel and touch events on the IPC background queue before hopping to the main thread, which is also why the class opted into the thread-safe checked-ptr variant previously. Each subclass previously took a WebEvent&& and move-constructed its base, which forced callers to materialize a stack WebEvent - precisely what the delete check cannot tolerate, and impossible once the base is refcounted, since ThreadSafeRefCountedBase holds a std::atomic and so has no copy or move constructor. That pattern is replaced by plain data structs: WebEventData for the fields common to every event, plus a per-class WebMouseEventData / WebKeyboardEventData / WebWheelEventData / WebTouchEventData / WebGestureEventData. Each event class stores exactly one of these and its accessors delegate to it. The per-port WebEventFactory functions returned event values, and each NativeWebXEvent constructor sliced its base from one. They now return a WebXEventInit (a WebEventData plus a WebXEventData), which both WebXEvent::create() and NativeWebXEvent::create() consume, so a native event is still only parsed once. Fields that no constructor on a given platform ever set are no longer stored, and their accessors return the constant instead: pointerId/pointerType on MAC, unmodifiedText/macCharCode/ isSystemKey on GTK and WPE, and directionInvertedFromDevice/momentumEndType off COCOA. This is what makes "the data struct is exactly the serialized fields" true on every platform. The IPC wire format is unchanged. Each event class is [RefCounted] in WebEvent.serialization.in with two members, eventData() and xData(), whose fields are declared in the same order as before, so the same values are encoded in the same sequence. Predicted mouse events are deliberately still not serialized - WebMouseEvent keeps them outside its data struct - because putting them on the wire would switch on a currently dormant PointerEvent.getPredictedEvents() path for mouse. The data structs are [CustomHeader] since they live in their event class's header rather than one named after themselves. Three sites copied an event and then mutated the copy, which was safe only while events had value semantics: EventDispatcher coalescing touch moves, WebPageProxy::sendWheelEvent capturing the event before adjusting its position, and the remote-frame transform in WebPageIOS. They now call an explicit copy(). WebTouchEvent::copy() is a deep copy because transformToRemoteFrameCoordinates() mutates the coalesced and predicted children in place. Relatedly, processNextQueuedMouseEvent() appended the event into the very vector the event was about to own, which as a Ref would have been a reference cycle; it now appends a copy. Every concrete class gets its own WTF_MAKE_TZONE_ALLOCATED. Allocating subclasses through WebEvent's allocator would compile, but TZoneHeap's operator new only takes the fast path when size == sizeof(the declaring type), so every event would have fallen through to operatorNewSlow and lost TZone's per-type isolation. WebWheelEvent::Granularity and ::MomentumEndType move to namespace scope as WebWheelEventGranularity and WebWheelEventMomentumEndType, with using aliases left in the class, because a namespace-scope WebWheelEventData cannot name enums nested inside WebWheelEvent. All existing WebWheelEvent::Granularity spellings keep working. The two remaining CheckedPtr clients become RefPtr: CurrentEvent::m_previousCurrentEvent in WebPage.cpp and the local in PluginView.cpp. Both are synchronous and the event is guaranteed live, so an owning reference is both cheaper and safer than a checked one. The g_currentEvent global stays a raw pointer so that dispatching an event, which happens for every mouse move, does not touch a refcount. * Source/WebKit/Shared/NativeWebGestureEvent.h: * Source/WebKit/Shared/NativeWebKeyboardEvent.h: * Source/WebKit/Shared/NativeWebMouseEvent.h: * Source/WebKit/Shared/NativeWebTouchEvent.h: * Source/WebKit/Shared/NativeWebWheelEvent.h: * Source/WebKit/Shared/RemoteWebTouchEvent.h: * Source/WebKit/Shared/RemoteWebTouchEvent.serialization.in: * Source/WebKit/Shared/WebEvent.cpp: (WebKit::generateSignpostIdentifier): (WebKit::WebEvent::WebEvent): * Source/WebKit/Shared/WebEvent.h: (WebKit::WebEvent::type const): (WebKit::WebEvent::shiftKey const): (WebKit::WebEvent::controlKey const): (WebKit::WebEvent::altKey const): (WebKit::WebEvent::metaKey const): (WebKit::WebEvent::capsLockKey const): (WebKit::WebEvent::modifiers const): (WebKit::WebEvent::timestamp const): (WebKit::WebEvent::authorizationToken const): (WebKit::WebEvent::signpostIdentifier const): * Source/WebKit/Shared/WebEvent.serialization.in: * Source/WebKit/Shared/WebEventConversion.cpp: (WebKit::WebKit2PlatformMouseEvent::WebKit2PlatformMouseEvent): (WebKit::WebKit2PlatformTouchEvent::WebKit2PlatformTouchEvent): * Source/WebKit/Shared/WebKeyboardEvent.cpp: (WebKit::WebKeyboardEvent::create): (WebKit::WebKeyboardEvent::WebKeyboardEvent): * Source/WebKit/Shared/WebKeyboardEvent.h: (WebKit::WebKeyboardEvent::windowsVirtualKeyCode const): (WebKit::WebKeyboardEvent::setWindowsVirtualKeyCode): (WebKit::WebKeyboardEvent::nativeVirtualKeyCode const): (WebKit::WebKeyboardEvent::macCharCode const): (WebKit::WebKeyboardEvent::handledByInputMethod const): (WebKit::WebKeyboardEvent::isAutoRepeat const): (WebKit::WebKeyboardEvent::isKeypad const): (WebKit::WebKeyboardEvent::isSystemKey const): * Source/WebKit/Shared/WebMouseEvent.cpp: (WebKit::WebMouseEvent::create): (WebKit::WebMouseEvent::copy const): (WebKit::WebMouseEvent::WebMouseEvent): * Source/WebKit/Shared/WebMouseEvent.h: (WebKit::WebMouseEvent::button const): (WebKit::WebMouseEvent::buttons const): (WebKit::WebMouseEvent::setPosition): (WebKit::WebMouseEvent::deltaX const): (WebKit::WebMouseEvent::deltaY const): (WebKit::WebMouseEvent::deltaZ const): (WebKit::WebMouseEvent::clickCount const): (WebKit::WebMouseEvent::eventNumber const): (WebKit::WebMouseEvent::menuTypeForEvent const): (WebKit::WebMouseEvent::isTouchEvent const): (WebKit::WebMouseEvent::force const): (WebKit::WebMouseEvent::inputSource const): (WebKit::WebMouseEvent::canInitiateDrag const): (WebKit::WebMouseEvent::syntheticClickType const): (WebKit::WebMouseEvent::pointerId const): (WebKit::WebMouseEvent::gestureWasCancelled const): (WebKit::WebMouseEvent::setCoalescedEvents): (WebKit::WebMouseEvent::setPredictedEvents): (WebKit::WebMouseEvent::WebMouseEvent): Deleted. (WebKit::WebMouseEvent::position const): Deleted. (WebKit::WebMouseEvent::coalescedEvents const): Deleted. (WebKit::WebMouseEvent::predictedEvents const): Deleted. * Source/WebKit/Shared/WebTouchEvent.cpp: (WebKit::WebTouchEvent::create): (WebKit::WebTouchEvent::WebTouchEvent): (WebKit::WebTouchEvent::copy const): (WebKit::WebTouchEvent::transformToRemoteFrameCoordinates): * Source/WebKit/Shared/WebTouchEvent.h: (WebKit::WebTouchEvent::setCoalescedEvents): (WebKit::WebTouchEvent::setPredictedEvents): (WebKit::WebTouchEvent::position const): (WebKit::WebTouchEvent::isPotentialTap const): (WebKit::WebTouchEvent::isGesture const): (WebKit::WebTouchEvent::gestureScale const): (WebKit::WebTouchEvent::gestureRotation const): (WebKit::WebTouchEvent::canPreventNativeGestures const): (WebKit::WebTouchEvent::setCanPreventNativeGestures): (WebKit::WebTouchEvent::WebTouchEvent): Deleted. * Source/WebKit/Shared/WebWheelEvent.cpp: (WebKit::WebWheelEvent::create): (WebKit::WebWheelEvent::copy const): (WebKit::WebWheelEvent::WebWheelEvent): * Source/WebKit/Shared/WebWheelEvent.h: (WebKit::WebWheelEvent::position const): (WebKit::WebWheelEvent::setPosition): (WebKit::WebWheelEvent::globalPosition const): (WebKit::WebWheelEvent::delta const): (WebKit::WebWheelEvent::wheelTicks const): (WebKit::WebWheelEvent::granularity const): (WebKit::WebWheelEvent::directionInvertedFromDevice const): (WebKit::WebWheelEvent::momentumEndType const): (WebKit::WebWheelEvent::phase const): (WebKit::WebWheelEvent::momentumPhase const): (WebKit::WebWheelEvent::hasPreciseScrollingDeltas const): (WebKit::WebWheelEvent::ioHIDEventTimestamp const): (WebKit::WebWheelEvent::rawPlatformDelta const): (WebKit::WebWheelEvent::setRawPlatformDelta): (WebKit::WebWheelEvent::scrollCount const): (WebKit::WebWheelEvent::inputSource const): (WebKit::WebWheelEvent::momentumFastScrollMultiplier const): (WebKit::WebWheelEvent::setMomentumFastScrollMultiplier): * Source/WebKit/Shared/WebWheelEventCoalescer.cpp: (WebKit::WebWheelEventCoalescer::coalesce): (WebKit::WebWheelEventCoalescer::nextEventToDispatch): (WebKit::WebWheelEventCoalescer::shouldDispatchEvent): (WebKit::WebWheelEventCoalescer::takeOldestEventBeingProcessed): (WebKit::operator<<): Deleted. * Source/WebKit/Shared/WebWheelEventCoalescer.h: * Source/WebKit/Shared/gtk/NativeWebKeyboardEventGtk.cpp: (WebKit::NativeWebKeyboardEvent::create): (WebKit::NativeWebKeyboardEvent::NativeWebKeyboardEvent): (): Deleted. * Source/WebKit/Shared/gtk/NativeWebMouseEventGtk.cpp: (WebKit::NativeWebMouseEvent::create): (WebKit::NativeWebMouseEvent::NativeWebMouseEvent): * Source/WebKit/Shared/gtk/NativeWebTouchEventGtk.cpp: (WebKit::NativeWebTouchEvent::create): (WebKit::NativeWebTouchEvent::NativeWebTouchEvent): (): Deleted. * Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp: (WebKit::NativeWebWheelEvent::create): (WebKit::NativeWebWheelEvent::NativeWebWheelEvent): * Source/WebKit/Shared/gtk/WebEventFactory.cpp: (WebKit::WebEventFactory::createWebMouseEvent): (WebKit::WebEventFactory::createWebKeyboardEvent): (WebKit::WebEventFactory::createWebTouchEvent): (WebKit::WebEventFactory::createWebWheelEvent): * Source/WebKit/Shared/gtk/WebEventFactory.h: * Source/WebKit/Shared/ios/NativeWebKeyboardEventIOS.mm: (WebKit::NativeWebKeyboardEvent::create): (WebKit::NativeWebKeyboardEvent::NativeWebKeyboardEvent): * Source/WebKit/Shared/ios/NativeWebMouseEventIOS.mm: (WebKit::NativeWebMouseEvent::create): (WebKit::NativeWebMouseEvent::NativeWebMouseEvent): (): Deleted. * Source/WebKit/Shared/ios/NativeWebTouchEventIOS.mm: (WebKit::NativeWebTouchEvent::extractCoalescedWebTouchEvents): (WebKit::NativeWebTouchEvent::extractPredictedWebTouchEvents): (WebKit::NativeWebTouchEvent::create): (WebKit::NativeWebTouchEvent::NativeWebTouchEvent): (): Deleted. * Source/WebKit/Shared/ios/WebIOSEventFactory.h: * Source/WebKit/Shared/ios/WebIOSEventFactory.mm: (WebKit::WebIOSEventFactory::createWebKeyboardEvent): (WebKit::WebIOSEventFactory::createWebMouseEvent): (WebKit::WebIOSEventFactory::createWebWheelEvent): * Source/WebKit/Shared/libwpe/NativeWebKeyboardEventLibWPE.cpp: (WebKit::NativeWebKeyboardEvent::create): (WebKit::NativeWebKeyboardEvent::NativeWebKeyboardEvent): * Source/WebKit/Shared/libwpe/NativeWebMouseEventLibWPE.cpp: (WebKit::NativeWebMouseEvent::create): (WebKit::NativeWebMouseEvent::NativeWebMouseEvent): * Source/WebKit/Shared/libwpe/NativeWebTouchEventLibWPE.cpp: (WebKit::NativeWebTouchEvent::create): (WebKit::NativeWebTouchEvent::NativeWebTouchEvent): * Source/WebKit/Shared/libwpe/NativeWebWheelEventLibWPE.cpp: (WebKit::NativeWebWheelEvent::create): (WebKit::NativeWebWheelEvent::NativeWebWheelEvent): * Source/WebKit/Shared/libwpe/WebEventFactory.cpp: (WebKit::WebEventFactory::createWebKeyboardEvent): (WebKit::WebEventFactory::createWebMouseEvent): (WebKit::WebEventFactory::createWebWheelEvent): (WebKit::WebEventFactory::createWebTouchEvent): * Source/WebKit/Shared/libwpe/WebEventFactory.h: * Source/WebKit/Shared/mac/NativeWebGestureEventMac.mm: (WebKit::NativeWebGestureEvent::create): (WebKit::NativeWebGestureEvent::NativeWebGestureEvent): * Source/WebKit/Shared/mac/NativeWebKeyboardEventMac.mm: (WebKit::NativeWebKeyboardEvent::create): (WebKit::NativeWebKeyboardEvent::NativeWebKeyboardEvent): * Source/WebKit/Shared/mac/NativeWebMouseEventMac.mm: (WebKit::NativeWebMouseEvent::create): (WebKit::NativeWebMouseEvent::NativeWebMouseEvent): * Source/WebKit/Shared/mac/NativeWebWheelEventMac.mm: (WebKit::NativeWebWheelEvent::create): (WebKit::NativeWebWheelEvent::NativeWebWheelEvent): * Source/WebKit/Shared/mac/WebEventFactory.h: * Source/WebKit/Shared/mac/WebEventFactory.mm: (WebKit::WebEventFactory::createWebMouseEvent): (WebKit::WebEventFactory::createWebWheelEvent): (WebKit::WebEventFactory::createWebKeyboardEvent): * Source/WebKit/Shared/mac/WebGestureEvent.cpp: (WebKit::WebGestureEvent::create): (WebKit::WebGestureEvent::WebGestureEvent): (WebKit::WebGestureEvent::isGestureEventType): (WebKit::WebGestureEvent::isGestureEventType const): Deleted. * Source/WebKit/Shared/mac/WebGestureEvent.h: (WebKit::WebGestureEvent::position const): (WebKit::WebGestureEvent::gestureScale const): (WebKit::WebGestureEvent::gestureRotation const): (WebKit::WebGestureEvent::phase const): (WebKit::WebGestureEvent::WebGestureEvent): Deleted. * Source/WebKit/Shared/win/NativeWebKeyboardEventWin.cpp: (WebKit::NativeWebKeyboardEvent::create): (WebKit::NativeWebKeyboardEvent::NativeWebKeyboardEvent): * Source/WebKit/Shared/win/NativeWebMouseEventWin.cpp: (WebKit::NativeWebMouseEvent::create): (WebKit::NativeWebMouseEvent::NativeWebMouseEvent): * Source/WebKit/Shared/win/NativeWebTouchEventWin.cpp: (WebKit::NativeWebTouchEvent::create): (WebKit::NativeWebTouchEvent::NativeWebTouchEvent): * Source/WebKit/Shared/win/NativeWebWheelEventWin.cpp: (WebKit::NativeWebWheelEvent::create): (WebKit::NativeWebWheelEvent::NativeWebWheelEvent): * Source/WebKit/Shared/win/WebEventFactory.cpp: (WebKit::WebEventFactory::createWebMouseEvent): (WebKit::WebEventFactory::createWebWheelEvent): (WebKit::WebEventFactory::createWebKeyboardEvent): (WebKit::WebEventFactory::createWebTouchEvent): * Source/WebKit/Shared/win/WebEventFactory.h: * Source/WebKit/Shared/wpe/NativeWebKeyboardEventWPE.cpp: (WebKit::NativeWebKeyboardEvent::create): (WebKit::NativeWebKeyboardEvent::NativeWebKeyboardEvent): Deleted. (): Deleted. * Source/WebKit/Shared/wpe/NativeWebMouseEventWPE.cpp: (WebKit::NativeWebMouseEvent::create): (WebKit::NativeWebMouseEvent::NativeWebMouseEvent): Deleted. * Source/WebKit/Shared/wpe/NativeWebTouchEventWPE.cpp: (WebKit::NativeWebTouchEvent::create): (WebKit::NativeWebTouchEvent::NativeWebTouchEvent): * Source/WebKit/Shared/wpe/NativeWebWheelEventWPE.cpp: (WebKit::NativeWebWheelEvent::create): (WebKit::NativeWebWheelEvent::NativeWebWheelEvent): Deleted. * Source/WebKit/Shared/wpe/WebEventFactoryWPE.cpp: (WebKit::WebEventFactory::createWebMouseEvent): (WebKit::WebEventFactory::createWebWheelEvent): (WebKit::WebEventFactory::createWebKeyboardEvent): (WebKit::WebEventFactory::createWebTouchEvent): * Source/WebKit/UIProcess/API/C/playstation/WKPagePrivatePlayStation.cpp: (WKPageHandleKeyboardEvent): (WKPageHandleMouseEvent): (WKPageHandleWheelEvent): * Source/WebKit/UIProcess/API/C/wpe/WKPagePrivateWPE.cpp: (WKPageHandleKeyboardEvent): (WKPageHandleMouseEvent): * Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp: (webkitWebViewBaseKeyPressEvent): (webkitWebViewBaseKeyReleaseEvent): (webkitWebViewBaseKeyPressed): (webkitWebViewBaseKeyReleased): (webkitWebViewBaseHandleMouseEvent): (webkitWebViewBaseButtonPressed): (webkitWebViewBaseButtonReleased): (webkitWebViewBaseScrollEvent): (handleScroll): (webkitWebViewBaseEnter): (webkitWebViewBaseMotion): (webkitWebViewBaseLeave): (webkitWebViewBaseTouchEvent): (webkitWebViewBaseSynthesizeCompositionKeyPress): (webkitWebViewBaseSynthesizeMouseEvent): (webkitWebViewBaseSynthesizeKeyEvent): (webkitWebViewBaseSynthesizeWheelEvent): (webkitWebViewBaseSynthesizeTouchEvent): * Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp: (WebKit::PageClientImpl::doneWithTouchEvent): * Source/WebKit/UIProcess/API/wpe/WPEWebViewLegacy.cpp: (WKWPE::ViewLegacy::ViewLegacy): (WKWPE::ViewLegacy::handleKeyboardEvent): (WKWPE::ViewLegacy::synthesizeCompositionKeyPress): * Source/WebKit/UIProcess/API/wpe/WPEWebViewPlatform.cpp: (WKWPE::ViewPlatform::handleEvent): (WKWPE::ViewPlatform::handleGesture): (WKWPE::ViewPlatform::synthesizeCompositionKeyPress): * Source/WebKit/UIProcess/Automation/win/WebAutomationSessionWin.cpp: (WebKit::doMouseButtonEvent): (WebKit::WebAutomationSession::platformSimulateKeyboardInteraction): (WebKit::WebAutomationSession::platformSimulateKeySequence): * Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp: (WebKit::RemoteScrollingCoordinatorProxy::handleWheelEvent): (WebKit::RemoteScrollingCoordinatorProxy::continueWheelEventHandling): * Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h: * Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.h: * Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.mm: (WebKit::RemoteLayerTreeEventDispatcher::willHandleWheelEvent): (WebKit::RemoteLayerTreeEventDispatcher::handleWheelEvent): (WebKit::RemoteLayerTreeEventDispatcher::continueWheelEventHandling): * Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.h: * Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.mm: (WebKit::RemoteScrollingCoordinatorProxyMac::handleWheelEvent): * Source/WebKit/UIProcess/ViewGestureController.h: * Source/WebKit/UIProcess/WebPageProxy.cpp: (WebKit::WebPageProxy::setInitialFocus): (WebKit::removeOldRedundantEvent): (WebKit::WebPageProxy::sendMouseEvent): (WebKit::WebPageProxy::handleMouseEvent): (WebKit::WebPageProxy::dispatchMouseDidMoveOverElementAsynchronously): (WebKit::WebPageProxy::processNextQueuedMouseEvent): (WebKit::WebPageProxy::processNextQueuedGestureEvent): (WebKit::WebPageProxy::handleNativeWheelEvent): (WebKit::WebPageProxy::handleWheelEvent): (WebKit::WebPageProxy::continueWheelEventHandling): (WebKit::WebPageProxy::sendWheelEvent): (WebKit::WebPageProxy::wheelEventHandlingCompleted): (WebKit::WebPageProxy::sendKeyEvent): (WebKit::WebPageProxy::handleKeyboardEvent): (WebKit::WebPageProxy::sendGestureEvent): (WebKit::WebPageProxy::handleGestureEvent): (WebKit::WebPageProxy::processNextQueuedTouchEvent): (WebKit::WebPageProxy::handleTouchEvent): (WebKit::WebPageProxy::touchEventHandlingCompleted): (WebKit::WebPageProxy::Internals::currentlyProcessedMouseDownEvent): (WebKit::WebPageProxy::mouseEventHandlingCompleted): (WebKit::WebPageProxy::gestureEventHandlingCompleted): (WebKit::WebPageProxy::keyEventHandlingCompleted): * Source/WebKit/UIProcess/WebPageProxy.h: * Source/WebKit/UIProcess/WebPageProxyInternals.h: (WebKit::QueuedTouchEvents::QueuedTouchEvents): * Source/WebKit/UIProcess/gtk/PointerLockManager.cpp: (WebKit::PointerLockManager::handleMotion): * Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm: (-[WKContentView _touchEventsRecognized]): * Source/WebKit/UIProcess/ios/WKMouseInteraction.mm: (-[WKMouseInteraction createMouseEventWithType:wasCancelled:]): (-[WKMouseInteraction handleGameControllerMouseMove:deltaY:]): * Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm: (-[WKAppKitGestureController sendWheelEventForGesture:]): (-[WKAppKitGestureController startMomentumIfNeededForGesture:]): * Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm: (WebKit::ViewGestureController::PendingSwipeTracker::scrollEventCanStartSwipe): (WebKit::ViewGestureController::PendingSwipeTracker::scrollEventCanEndSwipe): (WebKit::ViewGestureController::PendingSwipeTracker::scrollEventCanInfluenceSwipe): (WebKit::ViewGestureController::PendingSwipeTracker::scrollEventGetScrollingDeltas): (WebKit::ViewGestureController::handleScrollWheelEvent): * Source/WebKit/UIProcess/mac/WKFullScreenWindowController.mm: (-[WKFullScreenWindowController finishedEnterFullScreenAnimation:]): * Source/WebKit/UIProcess/mac/WebPageProxyMac.mm: (WebKit::WebPageProxy::shouldDelayWindowOrderingForEvent): (WebKit::WebPageProxy::acceptsFirstMouse): (WebKit::WebPageProxy::interruptSyntheticMomentumScrolling): * Source/WebKit/UIProcess/mac/WebViewImpl.mm: (WebKit::WebViewImpl::becomeFirstResponder): (WebKit::WebViewImpl::acceptsFirstMouse): (WebKit::WebViewImpl::shouldDelayWindowOrderingForEvent): (WebKit::WebViewImpl::scheduleMouseDidMoveOverElement): (WebKit::WebViewImpl::pressureChangeWithEvent): (WebKit::WebViewImpl::tryToSwipeWithEvent): (WebKit::WebViewImpl::scrollWheel): (WebKit::WebViewImpl::performKeyEquivalent): (WebKit::WebViewImpl::keyUp): (WebKit::WebViewImpl::keyDown): (WebKit::WebViewImpl::flagsChanged): (WebKit::WebViewImpl::nativeMouseEventHandler): * Source/WebKit/UIProcess/win/WebPageProxyWin.cpp: (WebKit::WebPageProxy::dispatchPendingCharEvents): * Source/WebKit/UIProcess/win/WebView.cpp: (WebKit::WebView::onMouseEvent): (WebKit::WebView::onWheelEvent): (WebKit::WebView::onKeyEvent): * Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm: (WebKit::PDFPlugin::showContextMenuAtPoint): * Source/WebKit/WebProcess/Plugins/PDF/PDFPluginBase.mm: (WebKit::PDFPluginBase::showContextMenuAtPoint): * Source/WebKit/WebProcess/Plugins/PluginView.cpp: (WebKit::PluginView::handleEvent): * Source/WebKit/WebProcess/WebPage/EventDispatcher.cpp: (WebKit::EventDispatcher::internalWheelEvent): (WebKit::EventDispatcher::wheelEvent): (WebKit::EventDispatcher::gestureEvent): (WebKit::TouchEventData::TouchEventData): (WebKit::EventDispatcher::touchEvent): (WebKit::EventDispatcher::dispatchWheelEventViaMainThread): * Source/WebKit/WebProcess/WebPage/EventDispatcher.h: * Source/WebKit/WebProcess/WebPage/EventDispatcher.messages.in: * Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.cpp: (WebKit::MomentumEventDispatcher::handleWheelEvent): (WebKit::MomentumEventDispatcher::dispatchSyntheticMomentumEvent): (WebKit::MomentumEventDispatcher::didStartMomentumPhase): * Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.h: * Source/WebKit/WebProcess/WebPage/WebPage.cpp: (WebKit::WebPage::mouseEvent): (WebKit::WebPage::performHitTestForMouseEvent): (WebKit::WebPage::handleWheelEvent): (WebKit::WebPage::dispatchWheelEventWithoutScrolling): (WebKit::WebPage::keyEvent): (WebKit::WebPage::touchEvent): (WebKit::WebPage::setInitialFocus): * Source/WebKit/WebProcess/WebPage/WebPage.h: * Source/WebKit/WebProcess/WebPage/WebPage.messages.in: * Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm: (WebKit::transformEventIfNecessary): (WebKit::WebPage::dispatchAsynchronousTouchEvents): * Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm: (WebKit::WebPage::shouldDelayWindowOrderingEvent): (WebKit::WebPage::requestAcceptsFirstMouse): Canonical link: https://commits.webkit.org/320030@main --- Source/WebKit/Shared/NativeWebGestureEvent.h | 10 +- Source/WebKit/Shared/NativeWebKeyboardEvent.h | 35 ++- Source/WebKit/Shared/NativeWebMouseEvent.h | 39 ++- Source/WebKit/Shared/NativeWebTouchEvent.h | 34 ++- Source/WebKit/Shared/NativeWebWheelEvent.h | 29 ++- Source/WebKit/Shared/RemoteWebTouchEvent.h | 2 +- .../RemoteWebTouchEvent.serialization.in | 2 +- Source/WebKit/Shared/WebEvent.cpp | 32 +-- Source/WebKit/Shared/WebEvent.h | 61 +++-- .../WebKit/Shared/WebEvent.serialization.in | 181 +++++++------ Source/WebKit/Shared/WebEventConversion.cpp | 8 +- Source/WebKit/Shared/WebKeyboardEvent.cpp | 105 +------- Source/WebKit/Shared/WebKeyboardEvent.h | 129 ++++++---- Source/WebKit/Shared/WebMouseEvent.cpp | 57 ++-- Source/WebKit/Shared/WebMouseEvent.h | 135 ++++++---- Source/WebKit/Shared/WebTouchEvent.cpp | 42 ++- Source/WebKit/Shared/WebTouchEvent.h | 119 +++++---- Source/WebKit/Shared/WebWheelEvent.cpp | 56 ++-- Source/WebKit/Shared/WebWheelEvent.h | 141 ++++++---- .../WebKit/Shared/WebWheelEventCoalescer.cpp | 81 ++++-- Source/WebKit/Shared/WebWheelEventCoalescer.h | 12 +- .../Shared/gtk/NativeWebKeyboardEventGtk.cpp | 60 ++++- .../Shared/gtk/NativeWebMouseEventGtk.cpp | 55 +++- .../Shared/gtk/NativeWebTouchEventGtk.cpp | 27 +- .../Shared/gtk/NativeWebWheelEventGtk.cpp | 20 +- Source/WebKit/Shared/gtk/WebEventFactory.cpp | 117 ++++++--- Source/WebKit/Shared/gtk/WebEventFactory.h | 12 +- .../Shared/ios/NativeWebKeyboardEventIOS.mm | 12 +- .../Shared/ios/NativeWebMouseEventIOS.mm | 62 ++++- .../Shared/ios/NativeWebTouchEventIOS.mm | 43 ++-- Source/WebKit/Shared/ios/WebIOSEventFactory.h | 6 +- .../WebKit/Shared/ios/WebIOSEventFactory.mm | 70 +++-- .../libwpe/NativeWebKeyboardEventLibWPE.cpp | 12 +- .../libwpe/NativeWebMouseEventLibWPE.cpp | 12 +- .../libwpe/NativeWebTouchEventLibWPE.cpp | 12 +- .../libwpe/NativeWebWheelEventLibWPE.cpp | 12 +- .../WebKit/Shared/libwpe/WebEventFactory.cpp | 95 +++++-- Source/WebKit/Shared/libwpe/WebEventFactory.h | 18 +- .../Shared/mac/NativeWebGestureEventMac.mm | 29 ++- .../Shared/mac/NativeWebKeyboardEventMac.mm | 12 +- .../Shared/mac/NativeWebMouseEventMac.mm | 12 +- .../Shared/mac/NativeWebWheelEventMac.mm | 19 +- Source/WebKit/Shared/mac/WebEventFactory.h | 6 +- Source/WebKit/Shared/mac/WebEventFactory.mm | 69 ++++- Source/WebKit/Shared/mac/WebGestureEvent.cpp | 22 +- Source/WebKit/Shared/mac/WebGestureEvent.h | 45 ++-- .../Shared/win/NativeWebKeyboardEventWin.cpp | 15 +- .../Shared/win/NativeWebMouseEventWin.cpp | 15 +- .../Shared/win/NativeWebTouchEventWin.cpp | 12 +- .../Shared/win/NativeWebWheelEventWin.cpp | 15 +- Source/WebKit/Shared/win/WebEventFactory.cpp | 62 ++++- Source/WebKit/Shared/win/WebEventFactory.h | 8 +- .../Shared/wpe/NativeWebKeyboardEventWPE.cpp | 24 +- .../Shared/wpe/NativeWebMouseEventWPE.cpp | 5 +- .../Shared/wpe/NativeWebTouchEventWPE.cpp | 10 +- .../Shared/wpe/NativeWebWheelEventWPE.cpp | 9 +- .../WebKit/Shared/wpe/WebEventFactoryWPE.cpp | 86 ++++--- .../playstation/WKPagePrivatePlayStation.cpp | 8 +- .../UIProcess/API/C/wpe/WKPagePrivateWPE.cpp | 8 +- .../UIProcess/API/gtk/WebKitWebViewBase.cpp | 44 ++-- .../WebKit/UIProcess/API/ios/WKWebViewIOS.mm | 4 +- .../UIProcess/API/wpe/PageClientImpl.cpp | 6 +- .../UIProcess/API/wpe/WPEWebViewLegacy.cpp | 20 +- .../UIProcess/API/wpe/WPEWebViewPlatform.cpp | 28 +- .../Automation/ios/WebAutomationSessionIOS.mm | 2 +- .../win/WebAutomationSessionWin.cpp | 10 +- .../RemoteScrollingCoordinatorProxy.cpp | 10 +- .../RemoteScrollingCoordinatorProxy.h | 4 +- .../mac/RemoteLayerTreeEventDispatcher.h | 6 +- .../mac/RemoteLayerTreeEventDispatcher.mm | 18 +- .../mac/RemoteScrollingCoordinatorProxyMac.h | 2 +- .../mac/RemoteScrollingCoordinatorProxyMac.mm | 4 +- .../WebKit/UIProcess/ViewGestureController.h | 4 +- Source/WebKit/UIProcess/WebPageProxy.cpp | 243 +++++++++--------- Source/WebKit/UIProcess/WebPageProxy.h | 40 +-- .../WebKit/UIProcess/WebPageProxyInternals.h | 16 +- .../UIProcess/gtk/PointerLockManager.cpp | 2 +- .../UIProcess/ios/WKContentViewInteraction.mm | 19 +- .../WebKit/UIProcess/ios/WKMouseInteraction.h | 2 +- .../UIProcess/ios/WKMouseInteraction.mm | 20 +- .../WebKit/UIProcess/ios/WebPageProxyIOS.mm | 2 +- .../WKAppKitGestureController.mm | 77 +++--- .../UIProcess/mac/ViewGestureControllerMac.mm | 10 +- .../mac/WKFullScreenWindowController.mm | 4 +- .../WebKit/UIProcess/mac/WebPageProxyMac.mm | 40 ++- Source/WebKit/UIProcess/mac/WebViewImpl.mm | 40 +-- .../WebKit/UIProcess/win/WebPageProxyWin.cpp | 6 +- Source/WebKit/UIProcess/win/WebView.cpp | 12 +- .../WebProcess/Plugins/PDF/PDFPlugin.mm | 9 +- .../WebProcess/Plugins/PDF/PDFPluginBase.mm | 10 +- .../WebKit/WebProcess/Plugins/PluginView.cpp | 2 +- .../WebProcess/WebPage/EventDispatcher.cpp | 55 ++-- .../WebProcess/WebPage/EventDispatcher.h | 14 +- .../WebPage/EventDispatcher.messages.in | 8 +- .../WebPage/MomentumEventDispatcher.cpp | 40 +-- .../WebPage/MomentumEventDispatcher.h | 4 +- Source/WebKit/WebProcess/WebPage/WebPage.cpp | 31 ++- Source/WebKit/WebProcess/WebPage/WebPage.h | 20 +- .../WebProcess/WebPage/WebPage.messages.in | 18 +- .../WebProcess/WebPage/ios/WebPageIOS.mm | 13 +- .../WebProcess/WebPage/mac/WebPageMac.mm | 6 +- 101 files changed, 2021 insertions(+), 1350 deletions(-) diff --git a/Source/WebKit/Shared/NativeWebGestureEvent.h b/Source/WebKit/Shared/NativeWebGestureEvent.h index a19712a000e1..16ac4523851f 100644 --- a/Source/WebKit/Shared/NativeWebGestureEvent.h +++ b/Source/WebKit/Shared/NativeWebGestureEvent.h @@ -35,6 +35,7 @@ OBJC_CLASS NSView; namespace WebKit { class NativeWebGestureEvent final : public WebGestureEvent { + WTF_MAKE_TZONE_ALLOCATED(NativeWebGestureEvent); public: // Distinguishes magnify from rotate without needing a backing NSEvent. enum class Kind : uint8_t { Magnification, Rotation }; @@ -49,16 +50,17 @@ class NativeWebGestureEvent final : public WebGestureEvent { bool allowsNativeZoom { true }; }; - static std::optional create(NSEvent *, NSView *); - static std::optional create(const Init&, NSView *); + // Null when the gesture phase does not map to a WebEventType. + static RefPtr create(NSEvent *, NSView *); + static RefPtr create(const Init&, NSView *); bool allowsNativeZoom() const { return m_allowsNativeZoom; } Kind kind() const { return m_kind; } NSEvent *nativeEvent() const { return m_nativeEvent.get(); } private: - static std::optional create(const Init&, NSView *, NSEvent *); - explicit NativeWebGestureEvent(WebEventType, const Init&, NSView *, NSEvent *); + static RefPtr create(const Init&, NSView *, NSEvent *); + NativeWebGestureEvent(WebEventType, const Init&, NSView *, NSEvent *); bool m_allowsNativeZoom { true }; Kind m_kind; diff --git a/Source/WebKit/Shared/NativeWebKeyboardEvent.h b/Source/WebKit/Shared/NativeWebKeyboardEvent.h index 63cfe7fc9385..4552062084ec 100644 --- a/Source/WebKit/Shared/NativeWebKeyboardEvent.h +++ b/Source/WebKit/Shared/NativeWebKeyboardEvent.h @@ -66,32 +66,33 @@ namespace WebKit { struct EditingRange; class NativeWebKeyboardEvent : public WebKeyboardEvent { + WTF_MAKE_TZONE_ALLOCATED(NativeWebKeyboardEvent); public: #if USE(APPKIT) // FIXME: Share iOS's HandledByInputMethod enum here instead of passing a boolean. - NativeWebKeyboardEvent(NSEvent *, bool handledByInputMethod, bool replacesSoftSpace, const Vector&); + static Ref create(NSEvent *, bool handledByInputMethod, bool replacesSoftSpace, const Vector&); #elif PLATFORM(GTK) - NativeWebKeyboardEvent(const NativeWebKeyboardEvent&); - NativeWebKeyboardEvent(GdkEvent*, const String&, bool isAutoRepeat, Vector&& commands); - NativeWebKeyboardEvent(const String&, std::optional>&&, std::optional&&); - NativeWebKeyboardEvent(WebEventType, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, Vector&& commands, bool isAutoRepeat, bool isKeypad, OptionSet); + static Ref create(const NativeWebKeyboardEvent&); + static Ref create(GdkEvent*, const String&, bool isAutoRepeat, Vector&& commands); + static Ref create(const String&, std::optional>&&, std::optional&&); + static Ref create(WebEventType, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, Vector&& commands, bool isAutoRepeat, bool isKeypad, OptionSet); #elif PLATFORM(IOS_FAMILY) enum class HandledByInputMethod : bool { No, Yes }; - NativeWebKeyboardEvent(::WebEvent *, HandledByInputMethod); + static Ref create(::WebEvent *, HandledByInputMethod); #elif PLATFORM(WPE) #if USE(LIBWPE) enum class HandledByInputMethod : bool { No, Yes }; - NativeWebKeyboardEvent(struct wpe_input_keyboard_event*, const String&, bool isAutoRepeat, HandledByInputMethod, std::optional>&&, std::optional&&); + static Ref create(struct wpe_input_keyboard_event*, const String&, bool isAutoRepeat, HandledByInputMethod, std::optional>&&, std::optional&&); #endif #if ENABLE(WPE_PLATFORM) - NativeWebKeyboardEvent(WPEEvent*, const String&, bool isAutoRepeat); - NativeWebKeyboardEvent(const String&, std::optional>&&, std::optional&&); + static Ref create(WPEEvent*, const String&, bool isAutoRepeat); + static Ref create(const String&, std::optional>&&, std::optional&&); #endif #elif PLATFORM(PLAYSTATION) enum class HandledByInputMethod : bool { No, Yes }; - NativeWebKeyboardEvent(struct wpe_input_keyboard_event*, const String&, bool isAutoRepeat, HandledByInputMethod, std::optional>&&, std::optional&&); + static Ref create(struct wpe_input_keyboard_event*, const String&, bool isAutoRepeat, HandledByInputMethod, std::optional>&&, std::optional&&); #elif PLATFORM(WIN) - NativeWebKeyboardEvent(HWND, UINT message, WPARAM, LPARAM, Vector&& pendingCharEvents); + static Ref create(HWND, UINT message, WPARAM, LPARAM, Vector&& pendingCharEvents); #endif #if USE(APPKIT) @@ -109,16 +110,28 @@ class NativeWebKeyboardEvent : public WebKeyboardEvent { private: #if USE(APPKIT) + NativeWebKeyboardEvent(WebKeyboardEventInit&&, NSEvent *); + RetainPtr m_nativeEvent; #elif PLATFORM(GTK) && USE(GTK4) + NativeWebKeyboardEvent(WebKeyboardEventInit&&, GdkEvent*); + GRefPtr m_nativeEvent; #elif PLATFORM(GTK) + NativeWebKeyboardEvent(WebKeyboardEventInit&&, GdkEvent*); + GUniquePtr m_nativeEvent; #elif PLATFORM(IOS_FAMILY) + NativeWebKeyboardEvent(WebKeyboardEventInit&&, ::WebEvent *); + RetainPtr<::WebEvent> m_nativeEvent; #elif PLATFORM(WIN) + NativeWebKeyboardEvent(WebKeyboardEventInit&&, const MSG&, Vector&&); + MSG m_nativeEvent; Vector m_pendingCharEvents; +#else + explicit NativeWebKeyboardEvent(WebKeyboardEventInit&&); #endif }; diff --git a/Source/WebKit/Shared/NativeWebMouseEvent.h b/Source/WebKit/Shared/NativeWebMouseEvent.h index 3f1087581732..a0e9b79c9149 100644 --- a/Source/WebKit/Shared/NativeWebMouseEvent.h +++ b/Source/WebKit/Shared/NativeWebMouseEvent.h @@ -65,30 +65,31 @@ struct wpe_input_pointer_event; namespace WebKit { class NativeWebMouseEvent : public WebMouseEvent { + WTF_MAKE_TZONE_ALLOCATED(NativeWebMouseEvent); public: #if USE(APPKIT) - NativeWebMouseEvent(NSEvent *, NSEvent *lastPressureEvent, NSView *, WebEventInputSource, WebCore::PlatformMouseEvent::CanInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes); + static Ref create(NSEvent *, NSEvent *lastPressureEvent, NSView *, WebEventInputSource, WebCore::PlatformMouseEvent::CanInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes); #elif PLATFORM(GTK) - NativeWebMouseEvent(const NativeWebMouseEvent&); - NativeWebMouseEvent(GdkEvent*, int, std::optional); - NativeWebMouseEvent(GdkEvent*, const WebCore::DoublePoint&, int, std::optional); - NativeWebMouseEvent(WebEventType, WebMouseEventButton, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, int clickCount, OptionSet modifiers, std::optional, WebCore::PointerID, const String& pointerType, WebCore::PlatformMouseEvent::IsTouch isTouchEvent); - explicit NativeWebMouseEvent(const WebCore::DoublePoint&); + static Ref create(const NativeWebMouseEvent&); + static Ref create(GdkEvent*, int, std::optional); + static Ref create(GdkEvent*, const WebCore::DoublePoint&, int, std::optional); + static Ref create(WebEventType, WebMouseEventButton, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, int clickCount, OptionSet modifiers, std::optional, WebCore::PointerID, const String& pointerType, WebCore::PlatformMouseEvent::IsTouch isTouchEvent); + static Ref create(const WebCore::DoublePoint&); #elif PLATFORM(IOS_FAMILY) - NativeWebMouseEvent(::WebEvent *); - NativeWebMouseEvent(WebEventType, WebMouseEventButton, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, OptionSet, MonotonicTime timestamp, double force, GestureWasCancelled, const String& pointerType); - NativeWebMouseEvent(const NativeWebMouseEvent&, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ); + static Ref create(::WebEvent *); + static Ref create(WebEventType, WebMouseEventButton, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, OptionSet, MonotonicTime timestamp, double force, GestureWasCancelled, const String& pointerType); + static Ref create(const NativeWebMouseEvent&, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ); #elif PLATFORM(WPE) #if USE(LIBWPE) - NativeWebMouseEvent(struct wpe_input_pointer_event*, float deviceScaleFactor, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap); + static Ref create(struct wpe_input_pointer_event*, float deviceScaleFactor, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap); #endif #if ENABLE(WPE_PLATFORM) - explicit NativeWebMouseEvent(WPEEvent*); + static Ref create(WPEEvent*); #endif #elif PLATFORM(PLAYSTATION) - NativeWebMouseEvent(struct wpe_input_pointer_event*, float deviceScaleFactor, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap); + static Ref create(struct wpe_input_pointer_event*, float deviceScaleFactor, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap); #elif PLATFORM(WIN) - NativeWebMouseEvent(HWND, UINT message, WPARAM, LPARAM, bool, float deviceScaleFactor); + static Ref create(HWND, UINT message, WPARAM, LPARAM, bool, float deviceScaleFactor); #endif #if USE(APPKIT) @@ -105,15 +106,27 @@ class NativeWebMouseEvent : public WebMouseEvent { private: #if USE(APPKIT) + NativeWebMouseEvent(WebMouseEventInit&&, NSEvent *); + RetainPtr m_nativeEvent; #elif PLATFORM(GTK) && USE(GTK4) + NativeWebMouseEvent(WebMouseEventInit&&, GdkEvent*); + GRefPtr m_nativeEvent; #elif PLATFORM(GTK) + NativeWebMouseEvent(WebMouseEventInit&&, GdkEvent*); + GUniquePtr m_nativeEvent; #elif PLATFORM(IOS_FAMILY) + NativeWebMouseEvent(WebMouseEventInit&&, ::WebEvent *); + RetainPtr<::WebEvent> m_nativeEvent; #elif PLATFORM(WIN) + NativeWebMouseEvent(WebMouseEventInit&&, const MSG&); + MSG m_nativeEvent; +#else + explicit NativeWebMouseEvent(WebMouseEventInit&&); #endif }; diff --git a/Source/WebKit/Shared/NativeWebTouchEvent.h b/Source/WebKit/Shared/NativeWebTouchEvent.h index de35dbd1f290..8845f3af8098 100644 --- a/Source/WebKit/Shared/NativeWebTouchEvent.h +++ b/Source/WebKit/Shared/NativeWebTouchEvent.h @@ -56,48 +56,62 @@ struct WKTouchEvent; #if ENABLE(TOUCH_EVENTS) class NativeWebTouchEvent : public WebTouchEvent { + WTF_MAKE_TZONE_ALLOCATED(NativeWebTouchEvent); public: #if PLATFORM(IOS_FAMILY) #if defined(__OBJC__) - explicit NativeWebTouchEvent(const WKTouchEvent&, UIKeyModifierFlags); + static Ref create(const WKTouchEvent&, UIKeyModifierFlags); #endif #elif PLATFORM(GTK) - NativeWebTouchEvent(GdkEvent*, Vector&&); - NativeWebTouchEvent(WebEventType, OptionSet, Vector&&); - NativeWebTouchEvent(const NativeWebTouchEvent&); + static Ref create(GdkEvent*, Vector&&); + static Ref create(WebEventType, OptionSet, Vector&&); + static Ref create(const NativeWebTouchEvent&); const GdkEvent* nativeEvent() const { return m_nativeEvent.get(); } #elif PLATFORM(WPE) bool isNativeWebTouchEvent() const final { return true; } #if USE(LIBWPE) - NativeWebTouchEvent(struct wpe_input_touch_event*, float deviceScaleFactor); + static Ref create(struct wpe_input_touch_event*, float deviceScaleFactor); const struct wpe_input_touch_event_raw* nativeFallbackTouchPoint() const { return &m_fallbackTouchPoint; } #endif #if ENABLE(WPE_PLATFORM) - NativeWebTouchEvent(WPEEvent*, Vector&&); + static Ref create(WPEEvent*, Vector&&); WPEEvent* nativeEvent() const { return m_nativeEvent.get(); } #endif #elif PLATFORM(WIN) - NativeWebTouchEvent(); + static Ref create(); #endif private: #if PLATFORM(IOS_FAMILY) && defined(__OBJC__) - Vector extractWebTouchPoints(const WKTouchEvent&); - Vector extractCoalescedWebTouchEvents(const WKTouchEvent&, UIKeyModifierFlags); - Vector extractPredictedWebTouchEvents(const WKTouchEvent&, UIKeyModifierFlags); + explicit NativeWebTouchEvent(WebTouchEventInit&&); + + static Vector extractWebTouchPoints(const WKTouchEvent&); + static Vector> extractCoalescedWebTouchEvents(const WKTouchEvent&, UIKeyModifierFlags); + static Vector> extractPredictedWebTouchEvents(const WKTouchEvent&, UIKeyModifierFlags); #endif #if PLATFORM(GTK) && USE(GTK4) + NativeWebTouchEvent(WebTouchEventInit&&, GdkEvent*); + GRefPtr m_nativeEvent; #elif PLATFORM(GTK) + NativeWebTouchEvent(WebTouchEventInit&&, GdkEvent*); + GUniquePtr m_nativeEvent; #elif PLATFORM(WPE) #if USE(LIBWPE) + NativeWebTouchEvent(WebTouchEventInit&&, struct wpe_input_touch_event*); + struct wpe_input_touch_event_raw m_fallbackTouchPoint; #endif #if ENABLE(WPE_PLATFORM) + NativeWebTouchEvent(WebTouchEventInit&&, WPEEvent*); + GRefPtr m_nativeEvent; #endif +#elif PLATFORM(WIN) + // IOS_FAMILY declares this above, alongside the WKTouchEvent helpers. + explicit NativeWebTouchEvent(WebTouchEventInit&&); #endif }; diff --git a/Source/WebKit/Shared/NativeWebWheelEvent.h b/Source/WebKit/Shared/NativeWebWheelEvent.h index b5361fedd7d9..a02150f5d003 100644 --- a/Source/WebKit/Shared/NativeWebWheelEvent.h +++ b/Source/WebKit/Shared/NativeWebWheelEvent.h @@ -58,25 +58,26 @@ struct wpe_input_axis_event; namespace WebKit { class NativeWebWheelEvent : public WebWheelEvent { + WTF_MAKE_TZONE_ALLOCATED(NativeWebWheelEvent); public: #if USE(APPKIT) - NativeWebWheelEvent(NSEvent *, NSView *); - explicit NativeWebWheelEvent(const WebWheelEvent&); + static Ref create(NSEvent *, NSView *); + static Ref create(const WebWheelEvent&); #elif PLATFORM(GTK) - NativeWebWheelEvent(const NativeWebWheelEvent&); - NativeWebWheelEvent(GdkEvent*, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase, bool hasPreciseDeltas = false); + static Ref create(const NativeWebWheelEvent&); + static Ref create(GdkEvent*, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase, bool hasPreciseDeltas = false); #elif PLATFORM(WPE) #if USE(LIBWPE) - NativeWebWheelEvent(struct wpe_input_axis_event*, float deviceScaleFactor, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase); + static Ref create(struct wpe_input_axis_event*, float deviceScaleFactor, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase); #endif #if ENABLE(WPE_PLATFORM) - explicit NativeWebWheelEvent(WPEEvent*); - NativeWebWheelEvent(WPEEvent*, WebWheelEvent::Phase); + static Ref create(WPEEvent*); + static Ref create(WPEEvent*, WebWheelEvent::Phase); #endif #elif PLATFORM(PLAYSTATION) - NativeWebWheelEvent(struct wpe_input_axis_event*, float deviceScaleFactor, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase); + static Ref create(struct wpe_input_axis_event*, float deviceScaleFactor, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase); #elif PLATFORM(WIN) - NativeWebWheelEvent(HWND, UINT message, WPARAM, LPARAM, float deviceScaleFactor); + static Ref create(HWND, UINT message, WPARAM, LPARAM, float deviceScaleFactor); #endif #if USE(APPKIT) @@ -91,13 +92,23 @@ class NativeWebWheelEvent : public WebWheelEvent { private: #if USE(APPKIT) + NativeWebWheelEvent(WebWheelEventInit&&, NSEvent *); + RetainPtr m_nativeEvent; #elif PLATFORM(GTK) && USE(GTK4) + NativeWebWheelEvent(WebWheelEventInit&&, GdkEvent*); + GRefPtr m_nativeEvent; #elif PLATFORM(GTK) + NativeWebWheelEvent(WebWheelEventInit&&, GdkEvent*); + GUniquePtr m_nativeEvent; #elif PLATFORM(WIN) + NativeWebWheelEvent(WebWheelEventInit&&, const MSG&); + MSG m_nativeEvent; +#else + explicit NativeWebWheelEvent(WebWheelEventInit&&); #endif }; diff --git a/Source/WebKit/Shared/RemoteWebTouchEvent.h b/Source/WebKit/Shared/RemoteWebTouchEvent.h index 72f2dccfdb0e..325a5776d0a4 100644 --- a/Source/WebKit/Shared/RemoteWebTouchEvent.h +++ b/Source/WebKit/Shared/RemoteWebTouchEvent.h @@ -33,7 +33,7 @@ namespace WebKit { #if ENABLE(TOUCH_EVENTS) struct RemoteWebTouchEvent { WebCore::FrameIdentifier targetFrameID; - WebTouchEvent transformedEvent; + Ref transformedEvent; }; #endif diff --git a/Source/WebKit/Shared/RemoteWebTouchEvent.serialization.in b/Source/WebKit/Shared/RemoteWebTouchEvent.serialization.in index 2bb7ae44153c..062a34584777 100644 --- a/Source/WebKit/Shared/RemoteWebTouchEvent.serialization.in +++ b/Source/WebKit/Shared/RemoteWebTouchEvent.serialization.in @@ -23,6 +23,6 @@ #if ENABLE(TOUCH_EVENTS) struct WebKit::RemoteWebTouchEvent { WebCore::FrameIdentifier targetFrameID; - WebKit::WebTouchEvent transformedEvent; + Ref transformedEvent; }; #endif diff --git a/Source/WebKit/Shared/WebEvent.cpp b/Source/WebKit/Shared/WebEvent.cpp index edcb4f0f4b40..f5d465ab8f8c 100644 --- a/Source/WebKit/Shared/WebEvent.cpp +++ b/Source/WebKit/Shared/WebEvent.cpp @@ -38,43 +38,19 @@ namespace WebKit { WTF_MAKE_TZONE_ALLOCATED_IMPL(WebEvent); #if PLATFORM(GTK) || PLATFORM(WPE) -static uintptr_t generateSignpostIdentifier() +uintptr_t generateSignpostIdentifier() { static std::atomic identifier; return ++identifier; } - -WebEvent::WebEvent(WebEventType type, OptionSet modifiers, MonotonicTime timestamp, WTF::UUID authorizationToken, uintptr_t signpostIdentifier) - : m_type(type) - , m_modifiers(modifiers) - , m_timestamp(timestamp) - , m_authorizationToken(authorizationToken) - , m_signpostIdentifier(signpostIdentifier) -{ -} #endif -WebEvent::WebEvent(WebEventType type, OptionSet modifiers, MonotonicTime timestamp, WTF::UUID authorizationToken) - : m_type(type) - , m_modifiers(modifiers) - , m_timestamp(timestamp) - , m_authorizationToken(authorizationToken) -#if PLATFORM(GTK) || PLATFORM(WPE) - , m_signpostIdentifier(generateSignpostIdentifier()) -#endif +WebEvent::WebEvent(WebEventData&& data) + : m_data(WTF::move(data)) { } -WebEvent::WebEvent(WebEventType type, OptionSet modifiers, MonotonicTime timestamp) - : m_type(type) - , m_modifiers(modifiers) - , m_timestamp(timestamp) - , m_authorizationToken(WTF::UUID::createVersion4()) -#if PLATFORM(GTK) || PLATFORM(WPE) - , m_signpostIdentifier(generateSignpostIdentifier()) -#endif -{ -} +WebEvent::~WebEvent() = default; // https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event bool WebEvent::isActivationTriggeringEvent() const diff --git a/Source/WebKit/Shared/WebEvent.h b/Source/WebKit/Shared/WebEvent.h index 78b529ec78c2..9cc6e4b16eef 100644 --- a/Source/WebKit/Shared/WebEvent.h +++ b/Source/WebKit/Shared/WebEvent.h @@ -31,11 +31,10 @@ #include "WebEventModifier.h" #include "WebEventType.h" -#include #include #include -#include #include +#include #include #include @@ -48,45 +47,53 @@ namespace WebKit { enum class WebEventInputSource : uint8_t { UserDriven, Automation }; -class WebEvent : public CanMakeThreadSafeCheckedPtr { - WTF_MAKE_TZONE_ALLOCATED(WebEvent); - WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(WebEvent); -public: #if PLATFORM(GTK) || PLATFORM(WPE) - WebEvent(WebEventType, OptionSet, MonotonicTime timestamp, WTF::UUID authorizationToken, uintptr_t signpostIdentifier); +uintptr_t generateSignpostIdentifier(); +#endif + +// Plain data for the fields common to every WebEvent. Passed to the create() function of each +// concrete event class, which forwards it to the WebEvent constructor. +struct WebEventData { + WebEventType type; + OptionSet modifiers; + MonotonicTime timestamp; + WTF::UUID authorizationToken { WTF::UUID::createVersion4() }; +#if PLATFORM(GTK) || PLATFORM(WPE) + uintptr_t signpostIdentifier { generateSignpostIdentifier() }; #endif - WebEvent(WebEventType, OptionSet, MonotonicTime timestamp, WTF::UUID authorizationToken); - WebEvent(WebEventType, OptionSet, MonotonicTime timestamp); +}; - virtual ~WebEvent() = default; +class WebEvent : public ThreadSafeRefCounted { + WTF_MAKE_TZONE_ALLOCATED(WebEvent); +public: + virtual ~WebEvent(); - WebEventType type() const { return m_type; } + WebEventType type() const { return m_data.type; } - bool shiftKey() const { return m_modifiers.contains(WebEventModifier::ShiftKey); } - bool controlKey() const { return m_modifiers.contains(WebEventModifier::ControlKey); } - bool altKey() const { return m_modifiers.contains(WebEventModifier::AltKey); } - bool metaKey() const { return m_modifiers.contains(WebEventModifier::MetaKey); } - bool capsLockKey() const { return m_modifiers.contains(WebEventModifier::CapsLockKey); } + bool shiftKey() const { return modifiers().contains(WebEventModifier::ShiftKey); } + bool controlKey() const { return modifiers().contains(WebEventModifier::ControlKey); } + bool altKey() const { return modifiers().contains(WebEventModifier::AltKey); } + bool metaKey() const { return modifiers().contains(WebEventModifier::MetaKey); } + bool capsLockKey() const { return modifiers().contains(WebEventModifier::CapsLockKey); } - OptionSet modifiers() const { return m_modifiers; } + OptionSet modifiers() const { return m_data.modifiers; } - MonotonicTime timestamp() const { return m_timestamp; } + MonotonicTime timestamp() const { return m_data.timestamp; } bool NODELETE isActivationTriggeringEvent() const; - WTF::UUID authorizationToken() const { return m_authorizationToken; } + WTF::UUID authorizationToken() const { return m_data.authorizationToken; } #if PLATFORM(GTK) || PLATFORM(WPE) - uintptr_t signpostIdentifier() const { return m_signpostIdentifier; } + uintptr_t signpostIdentifier() const { return m_data.signpostIdentifier; } #endif + const WebEventData& eventData() const LIFETIME_BOUND { return m_data; } + +protected: + explicit WebEvent(WebEventData&&); + private: - WebEventType m_type; - OptionSet m_modifiers; - MonotonicTime m_timestamp; - WTF::UUID m_authorizationToken; -#if PLATFORM(GTK) || PLATFORM(WPE) - uintptr_t m_signpostIdentifier; -#endif + WebEventData m_data; }; WTF::TextStream& operator<<(WTF::TextStream&, WebEventType); diff --git a/Source/WebKit/Shared/WebEvent.serialization.in b/Source/WebKit/Shared/WebEvent.serialization.in index db3d623407ff..eeca53620abf 100644 --- a/Source/WebKit/Shared/WebEvent.serialization.in +++ b/Source/WebKit/Shared/WebEvent.serialization.in @@ -29,13 +29,13 @@ AltGraphKey }; -class WebKit::WebEvent { - WebKit::WebEventType type(); - OptionSet modifiers(); - MonotonicTime timestamp(); - WTF::UUID authorizationToken(); +[CustomHeader] struct WebKit::WebEventData { + WebKit::WebEventType type; + OptionSet modifiers; + MonotonicTime timestamp; + WTF::UUID authorizationToken; #if PLATFORM(GTK) || PLATFORM(WPE) - uintptr_t signpostIdentifier(); + uintptr_t signpostIdentifier; #endif }; @@ -64,58 +64,68 @@ enum class WebKit::WebEventType : uint32_t { #endif }; -class WebKit::WebKeyboardEvent : WebKit::WebEvent { - String text(); +[CustomHeader] struct WebKit::WebKeyboardEventData { + String text; #if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) - String unmodifiedText(); + String unmodifiedText; #endif - String key(); - String code(); - String keyIdentifier(); - int32_t windowsVirtualKeyCode(); - int32_t nativeVirtualKeyCode(); + String key; + String code; + String keyIdentifier; + int32_t windowsVirtualKeyCode; + int32_t nativeVirtualKeyCode; #if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) - int32_t macCharCode(); + int32_t macCharCode; #endif #if USE(APPKIT) || PLATFORM(IOS_FAMILY) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - bool handledByInputMethod(); + bool handledByInputMethod; #endif #if PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - std::optional> preeditUnderlines(); - std::optional preeditSelectionRange(); + std::optional> preeditUnderlines; + std::optional preeditSelectionRange; #endif #if USE(APPKIT) - Vector commands(); + Vector commands; #endif #if !USE(APPKIT) && PLATFORM(GTK) - Vector commands(); + Vector commands; #endif - bool isAutoRepeat(); - bool isKeypad(); + bool isAutoRepeat; + bool isKeypad; #if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) - bool isSystemKey(); + bool isSystemKey; #endif }; +[RefCounted] class WebKit::WebKeyboardEvent { + WebKit::WebEventData eventData(); + WebKit::WebKeyboardEventData keyboardData(); +}; + #if PLATFORM(IOS_FAMILY) && ENABLE(IOS_TOUCH_EVENTS) [Nested] enum class WebKit::WebPlatformTouchPoint::TouchType : bool #endif #if ENABLE(TOUCH_EVENTS) -class WebKit::WebTouchEvent : WebKit::WebEvent { - Vector touchPoints(); - Vector coalescedEvents(); - Vector predictedEvents(); +[CustomHeader] struct WebKit::WebTouchEventData { + Vector touchPoints; + Vector> coalescedEvents; + Vector> predictedEvents; #if PLATFORM(IOS_FAMILY) - WebCore::DoublePoint position(); - bool isPotentialTap(); - bool isGesture(); - float gestureScale(); - float gestureRotation(); - bool canPreventNativeGestures(); + WebCore::DoublePoint position; + bool isPotentialTap; + bool isGesture; + float gestureScale; + float gestureRotation; + bool canPreventNativeGestures; #endif }; +[RefCounted] class WebKit::WebTouchEvent { + WebKit::WebEventData eventData(); + WebKit::WebTouchEventData touchData(); +}; + [Nested] enum class WebKit::WebPlatformTouchPoint::State : uint8_t { Released, Pressed, @@ -179,33 +189,38 @@ enum class WebCore::MouseEventCanInitiateDrag : bool; using WebCore::PointerID = uint32_t; -class WebKit::WebMouseEvent : WebKit::WebEvent { - WebKit::WebMouseEventButton button(); - unsigned short buttons(); - WebCore::DoublePoint position(); - WebCore::DoublePoint globalPosition(); - float deltaX(); - float deltaY(); - float deltaZ(); - int32_t clickCount(); - double force(); - WebKit::WebEventInputSource inputSource(); - WebCore::MouseEventCanInitiateDrag canInitiateDrag(); - WebKit::WebMouseEventSyntheticClickType syntheticClickType(); +[CustomHeader] struct WebKit::WebMouseEventData { + WebKit::WebMouseEventButton button; + unsigned short buttons; + WebCore::DoublePoint position; + WebCore::DoublePoint globalPosition; + float deltaX; + float deltaY; + float deltaZ; + int32_t clickCount; + double force; + WebKit::WebEventInputSource inputSource; + WebCore::MouseEventCanInitiateDrag canInitiateDrag; + WebKit::WebMouseEventSyntheticClickType syntheticClickType; #if PLATFORM(MAC) - int32_t eventNumber(); - int32_t menuTypeForEvent();; + int32_t eventNumber; + int32_t menuTypeForEvent; #endif #if !PLATFORM(MAC) && PLATFORM(GTK) - WebCore::PlatformMouseEvent::IsTouch isTouchEvent(); + WebCore::PlatformMouseEvent::IsTouch isTouchEvent; #endif #if !PLATFORM(MAC) - WebCore::PointerID pointerId(); - String pointerType(); + WebCore::PointerID pointerId; + String pointerType; #endif - WebKit::GestureWasCancelled gestureWasCancelled(); - WebCore::DoublePoint unadjustedMovementDelta(); - Vector coalescedEvents(); + WebKit::GestureWasCancelled gestureWasCancelled; + WebCore::DoublePoint unadjustedMovementDelta; + Vector> coalescedEvents; +}; + +[RefCounted] class WebKit::WebMouseEvent { + WebKit::WebEventData eventData(); + WebKit::WebMouseEventData mouseData(); }; #if !PLATFORM(MAC) && PLATFORM(GTK) @@ -213,15 +228,20 @@ class WebKit::WebMouseEvent : WebKit::WebEvent { #endif #if ENABLE(MAC_GESTURE_EVENTS) -class WebKit::WebGestureEvent : WebKit::WebEvent { - WebCore::IntPoint position(); - float gestureScale(); - float gestureRotation(); - WebKit::WebEventPhase phase(); +[CustomHeader] struct WebKit::WebGestureEventData { + WebCore::IntPoint position; + float gestureScale; + float gestureRotation; + WebKit::WebEventPhase phase; +}; + +[RefCounted] class WebKit::WebGestureEvent { + WebKit::WebEventData eventData(); + WebKit::WebGestureEventData gestureData(); }; #endif -[Nested] enum class WebKit::WebWheelEvent::Granularity : uint8_t { +enum class WebKit::WebWheelEventGranularity : uint8_t { ScrollByPageWheelEvent, ScrollByPixelWheelEvent }; @@ -237,34 +257,39 @@ enum class WebKit::WebEventPhase : uint8_t { WillBegin, }; -[Nested] enum class WebKit::WebWheelEvent::MomentumEndType : uint8_t { +enum class WebKit::WebWheelEventMomentumEndType : uint8_t { Unknown, Interrupted, Natural, }; -class WebKit::WebWheelEvent : WebKit::WebEvent { - WebCore::IntPoint position(); - WebCore::IntPoint globalPosition(); - WebCore::FloatSize delta(); - WebCore::FloatSize wheelTicks(); - WebKit::WebWheelEvent::Granularity granularity(); +[CustomHeader] struct WebKit::WebWheelEventData { + WebCore::IntPoint position; + WebCore::IntPoint globalPosition; + WebCore::FloatSize delta; + WebCore::FloatSize wheelTicks; + WebKit::WebWheelEventGranularity granularity; #if PLATFORM(COCOA) - bool directionInvertedFromDevice(); + bool directionInvertedFromDevice; #endif #if PLATFORM(COCOA) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - WebKit::WebEventPhase phase(); - WebKit::WebEventPhase momentumPhase(); - bool hasPreciseScrollingDeltas(); + WebKit::WebEventPhase phase; + WebKit::WebEventPhase momentumPhase; + bool hasPreciseScrollingDeltas; #endif #if PLATFORM(COCOA) - uint32_t scrollCount(); - WebCore::FloatSize unacceleratedScrollingDelta(); - MonotonicTime ioHIDEventTimestamp(); - std::optional rawPlatformDelta(); - WebKit::WebWheelEvent::MomentumEndType momentumEndType(); - WebKit::WebEventInputSource inputSource(); - float momentumFastScrollMultiplier(); + uint32_t scrollCount; + WebCore::FloatSize unacceleratedScrollingDelta; + MonotonicTime ioHIDEventTimestamp; + std::optional rawPlatformDelta; + WebKit::WebWheelEventMomentumEndType momentumEndType; + WebKit::WebEventInputSource inputSource; + float momentumFastScrollMultiplier; #endif }; +[RefCounted] class WebKit::WebWheelEvent { + WebKit::WebEventData eventData(); + WebKit::WebWheelEventData wheelData(); +}; + diff --git a/Source/WebKit/Shared/WebEventConversion.cpp b/Source/WebKit/Shared/WebEventConversion.cpp index 3ea9ca5f2ddc..6615e66b3a6a 100644 --- a/Source/WebKit/Shared/WebEventConversion.cpp +++ b/Source/WebKit/Shared/WebEventConversion.cpp @@ -294,10 +294,10 @@ class WebKit2PlatformMouseEvent : public WebCore::PlatformMouseEvent { m_clickCount = webEvent.clickCount(); m_force = forceForEvent(webEvent); m_coalescedEvents = WTF::map(webEvent.coalescedEvents(), [&](const auto& event) { - return platform(event); + return platform(event.get()); }); m_predictedEvents = WTF::map(webEvent.predictedEvents(), [&](const auto& event) { - return platform(event); + return platform(event.get()); }); m_inputSource = platform(webEvent.inputSource()); m_canInitiateDrag = webEvent.canInitiateDrag(); @@ -523,11 +523,11 @@ class WebKit2PlatformTouchEvent : public WebCore::PlatformTouchEvent { }); m_coalescedEvents = WTF::map(webEvent.coalescedEvents(), [&](auto& event) { - return platform(event); + return platform(event.get()); }); m_predictedEvents = WTF::map(webEvent.predictedEvents(), [&](auto& event) { - return platform(event); + return platform(event.get()); }); m_gestureScale = webEvent.gestureScale(); diff --git a/Source/WebKit/Shared/WebKeyboardEvent.cpp b/Source/WebKit/Shared/WebKeyboardEvent.cpp index fcadde7e024c..155236933650 100644 --- a/Source/WebKit/Shared/WebKeyboardEvent.cpp +++ b/Source/WebKit/Shared/WebKeyboardEvent.cpp @@ -27,116 +27,29 @@ #include "WebKeyboardEvent.h" #include +#include namespace WebKit { -#if USE(APPKIT) +WTF_MAKE_TZONE_ALLOCATED_IMPL(WebKeyboardEvent); -WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, const Vector& commands, bool isAutoRepeat, bool isKeypad, bool isSystemKey) - : WebEvent(WTF::move(event)) - , m_text(text) - , m_unmodifiedText(unmodifiedText) - , m_key(key) - , m_code(code) - , m_keyIdentifier(keyIdentifier) - , m_windowsVirtualKeyCode(windowsVirtualKeyCode) - , m_nativeVirtualKeyCode(nativeVirtualKeyCode) - , m_macCharCode(macCharCode) - , m_handledByInputMethod(handledByInputMethod) - , m_commands(commands) - , m_isAutoRepeat(isAutoRepeat) - , m_isKeypad(isKeypad) - , m_isSystemKey(isSystemKey) +Ref WebKeyboardEvent::create(WebEventData&& eventData, WebKeyboardEventData&& keyboardData) { - ASSERT(isKeyboardEventType(type())); + return adoptRef(*new WebKeyboardEvent(WTF::move(eventData), WTF::move(keyboardData))); } -#elif PLATFORM(GTK) - -WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, bool handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange, Vector&& commands, bool isAutoRepeat, bool isKeypad) - : WebEvent(WTF::move(event)) - , m_text(text) - , m_unmodifiedText(text) - , m_key(key) - , m_code(code) - , m_keyIdentifier(keyIdentifier) - , m_windowsVirtualKeyCode(windowsVirtualKeyCode) - , m_nativeVirtualKeyCode(nativeVirtualKeyCode) - , m_macCharCode(0) - , m_handledByInputMethod(handledByInputMethod) - , m_preeditUnderlines(WTF::move(preeditUnderlines)) - , m_preeditSelectionRange(WTF::move(preeditSelectionRange)) - , m_commands(WTF::move(commands)) - , m_isAutoRepeat(isAutoRepeat) - , m_isKeypad(isKeypad) - , m_isSystemKey(false) +Ref WebKeyboardEvent::create(WebKeyboardEventInit&& init) { - ASSERT(isKeyboardEventType(type())); + return create(WTF::move(init.event), WTF::move(init.keyboard)); } -#elif PLATFORM(IOS_FAMILY) - -WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, bool isAutoRepeat, bool isKeypad, bool isSystemKey) - : WebEvent(WTF::move(event)) - , m_text(text) - , m_unmodifiedText(unmodifiedText) - , m_key(key) - , m_code(code) - , m_keyIdentifier(keyIdentifier) - , m_windowsVirtualKeyCode(windowsVirtualKeyCode) - , m_nativeVirtualKeyCode(nativeVirtualKeyCode) - , m_macCharCode(macCharCode) - , m_handledByInputMethod(handledByInputMethod) - , m_isAutoRepeat(isAutoRepeat) - , m_isKeypad(isKeypad) - , m_isSystemKey(isSystemKey) +WebKeyboardEvent::WebKeyboardEvent(WebEventData&& eventData, WebKeyboardEventData&& keyboardData) + : WebEvent(WTF::move(eventData)) + , m_data(WTF::move(keyboardData)) { ASSERT(isKeyboardEventType(type())); } -#elif USE(LIBWPE) || ENABLE(WPE_PLATFORM) - -WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, bool handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange, bool isAutoRepeat, bool isKeypad) - : WebEvent(WTF::move(event)) - , m_text(text) - , m_unmodifiedText(text) - , m_key(key) - , m_code(code) - , m_keyIdentifier(keyIdentifier) - , m_windowsVirtualKeyCode(windowsVirtualKeyCode) - , m_nativeVirtualKeyCode(nativeVirtualKeyCode) - , m_macCharCode(0) - , m_handledByInputMethod(handledByInputMethod) - , m_preeditUnderlines(WTF::move(preeditUnderlines)) - , m_preeditSelectionRange(WTF::move(preeditSelectionRange)) - , m_isAutoRepeat(isAutoRepeat) - , m_isKeypad(isKeypad) - , m_isSystemKey(false) -{ - ASSERT(isKeyboardEventType(type())); -} - -#else - -WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool isAutoRepeat, bool isKeypad, bool isSystemKey) - : WebEvent(WTF::move(event)) - , m_text(text) - , m_unmodifiedText(unmodifiedText) - , m_key(key) - , m_code(code) - , m_keyIdentifier(keyIdentifier) - , m_windowsVirtualKeyCode(windowsVirtualKeyCode) - , m_nativeVirtualKeyCode(nativeVirtualKeyCode) - , m_macCharCode(macCharCode) - , m_isAutoRepeat(isAutoRepeat) - , m_isKeypad(isKeypad) - , m_isSystemKey(isSystemKey) -{ - ASSERT(isKeyboardEventType(type())); -} - -#endif - WebKeyboardEvent::~WebKeyboardEvent() = default; bool WebKeyboardEvent::isKeyboardEventType(WebEventType type) diff --git a/Source/WebKit/Shared/WebKeyboardEvent.h b/Source/WebKit/Shared/WebKeyboardEvent.h index 8915f44919a1..888b14ef70bd 100644 --- a/Source/WebKit/Shared/WebKeyboardEvent.h +++ b/Source/WebKit/Shared/WebKeyboardEvent.h @@ -36,48 +36,96 @@ namespace WebKit { +class WebKeyboardEvent; + +// Field order matches WebEvent.serialization.in. On GTK/WPE, unmodifiedText, macCharCode and +// isSystemKey are not stored because no constructor on those platforms sets them. +struct WebKeyboardEventData { + String text; +#if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) + String unmodifiedText; +#endif + String key; + String code; + String keyIdentifier; + int32_t windowsVirtualKeyCode { 0 }; + int32_t nativeVirtualKeyCode { 0 }; +#if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) + int32_t macCharCode { 0 }; +#endif +#if USE(APPKIT) || PLATFORM(IOS_FAMILY) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) + bool handledByInputMethod { false }; +#endif +#if PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) + std::optional> preeditUnderlines; + std::optional preeditSelectionRange; +#endif +#if USE(APPKIT) + Vector commands; +#elif PLATFORM(GTK) + Vector commands; +#endif + bool isAutoRepeat { false }; + bool isKeypad { false }; +#if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) + bool isSystemKey { false }; +#endif +}; + +struct WebKeyboardEventInit { + WebEventData event; + WebKeyboardEventData keyboard; +}; + class WebKeyboardEvent : public WebEvent { + WTF_MAKE_TZONE_ALLOCATED(WebKeyboardEvent); public: + static Ref create(WebEventData&&, WebKeyboardEventData&&); + static Ref create(WebKeyboardEventInit&&); + ~WebKeyboardEvent(); -#if USE(APPKIT) - WebKeyboardEvent(WebEvent&&, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, const Vector&, bool isAutoRepeat, bool isKeypad, bool isSystemKey); -#elif PLATFORM(GTK) - WebKeyboardEvent(WebEvent&&, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, bool handledByInputMethod, std::optional>&&, std::optional&&, Vector&& commands, bool isAutoRepeat, bool isKeypad); -#elif PLATFORM(IOS_FAMILY) - WebKeyboardEvent(WebEvent&&, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, bool isAutoRepeat, bool isKeypad, bool isSystemKey); -#elif USE(LIBWPE) || ENABLE(WPE_PLATFORM) - WebKeyboardEvent(WebEvent&&, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, bool handledByInputMethod, std::optional>&&, std::optional&&, bool isAutoRepeat, bool isKeypad); + const String& text() const LIFETIME_BOUND { return m_data.text; } +#if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) + const String& unmodifiedText() const LIFETIME_BOUND { return m_data.unmodifiedText; } #else - WebKeyboardEvent(WebEvent&&, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool isAutoRepeat, bool isKeypad, bool isSystemKey); + // Always identical to text() on this platform. + const String& unmodifiedText() const LIFETIME_BOUND { return m_data.text; } #endif - - const String& text() const LIFETIME_BOUND { return m_text; } - const String& unmodifiedText() const LIFETIME_BOUND { return m_unmodifiedText; } - const String& key() const LIFETIME_BOUND { return m_key; } - const String& code() const LIFETIME_BOUND { return m_code; } - const String& keyIdentifier() const LIFETIME_BOUND { return m_keyIdentifier; } - int32_t windowsVirtualKeyCode() const { return m_windowsVirtualKeyCode; } + const String& key() const LIFETIME_BOUND { return m_data.key; } + const String& code() const LIFETIME_BOUND { return m_data.code; } + const String& keyIdentifier() const LIFETIME_BOUND { return m_data.keyIdentifier; } + int32_t windowsVirtualKeyCode() const { return m_data.windowsVirtualKeyCode; } #if PLATFORM(WIN) - void setWindowsVirtualKeyCode(int32_t keyCode) { m_windowsVirtualKeyCode = keyCode; } + void setWindowsVirtualKeyCode(int32_t keyCode) { m_data.windowsVirtualKeyCode = keyCode; } +#endif + int32_t nativeVirtualKeyCode() const { return m_data.nativeVirtualKeyCode; } +#if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) + int32_t macCharCode() const { return m_data.macCharCode; } +#else + int32_t macCharCode() const { return 0; } #endif - int32_t nativeVirtualKeyCode() const { return m_nativeVirtualKeyCode; } - int32_t macCharCode() const { return m_macCharCode; } #if USE(APPKIT) || PLATFORM(IOS_FAMILY) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - bool handledByInputMethod() const { return m_handledByInputMethod; } + bool handledByInputMethod() const { return m_data.handledByInputMethod; } #endif #if PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - const std::optional>& preeditUnderlines() const LIFETIME_BOUND { return m_preeditUnderlines; } - const std::optional& preeditSelectionRange() const LIFETIME_BOUND { return m_preeditSelectionRange; } + const std::optional>& preeditUnderlines() const LIFETIME_BOUND { return m_data.preeditUnderlines; } + const std::optional& preeditSelectionRange() const LIFETIME_BOUND { return m_data.preeditSelectionRange; } #endif #if USE(APPKIT) - const Vector& commands() const LIFETIME_BOUND { return m_commands; } + const Vector& commands() const LIFETIME_BOUND { return m_data.commands; } #elif PLATFORM(GTK) - const Vector& commands() const LIFETIME_BOUND { return m_commands; } + const Vector& commands() const LIFETIME_BOUND { return m_data.commands; } +#endif + bool isAutoRepeat() const { return m_data.isAutoRepeat; } + bool isKeypad() const { return m_data.isKeypad; } +#if !PLATFORM(GTK) && !USE(LIBWPE) && !ENABLE(WPE_PLATFORM) + bool isSystemKey() const { return m_data.isSystemKey; } +#else + bool isSystemKey() const { return false; } #endif - bool isAutoRepeat() const { return m_isAutoRepeat; } - bool isKeypad() const { return m_isKeypad; } - bool isSystemKey() const { return m_isSystemKey; } + + const WebKeyboardEventData& keyboardData() const LIFETIME_BOUND { return m_data; } static bool NODELETE isKeyboardEventType(WebEventType); @@ -96,30 +144,11 @@ class WebKeyboardEvent : public WebEvent { static String singleCharacterStringForGdkKeyval(unsigned); #endif +protected: + WebKeyboardEvent(WebEventData&&, WebKeyboardEventData&&); + private: - String m_text; - String m_unmodifiedText; - String m_key; - String m_code; - String m_keyIdentifier; - int32_t m_windowsVirtualKeyCode { 0 }; - int32_t m_nativeVirtualKeyCode { 0 }; - int32_t m_macCharCode { 0 }; -#if USE(APPKIT) || PLATFORM(IOS_FAMILY) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - bool m_handledByInputMethod { false }; -#endif -#if PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - std::optional> m_preeditUnderlines; - std::optional m_preeditSelectionRange; -#endif -#if USE(APPKIT) - Vector m_commands; -#elif PLATFORM(GTK) - Vector m_commands; -#endif - bool m_isAutoRepeat { false }; - bool m_isKeypad { false }; - bool m_isSystemKey { false }; + WebKeyboardEventData m_data; }; } // namespace WebKit diff --git a/Source/WebKit/Shared/WebMouseEvent.cpp b/Source/WebKit/Shared/WebMouseEvent.cpp index 884bd871a816..0472a1472dbc 100644 --- a/Source/WebKit/Shared/WebMouseEvent.cpp +++ b/Source/WebKit/Shared/WebMouseEvent.cpp @@ -29,44 +29,33 @@ #include "WebEventConversion.h" #include #include +#include namespace WebKit { using namespace WebCore; -#if PLATFORM(MAC) -WebMouseEvent::WebMouseEvent(WebEvent&& event, WebMouseEventButton button, unsigned short buttons, const DoublePoint& positionInView, const DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, double force, WebEventInputSource inputSource, PlatformMouseEvent::CanInitiateDrag canInitiateDrag, WebMouseEventSyntheticClickType syntheticClickType, int eventNumber, int menuType, GestureWasCancelled gestureWasCancelled, const DoublePoint& unadjustedMovementDelta, const Vector& coalescedEvents, const Vector& predictedEvents) -#elif PLATFORM(GTK) -WebMouseEvent::WebMouseEvent(WebEvent&& event, WebMouseEventButton button, unsigned short buttons, const DoublePoint& positionInView, const DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, double force, WebEventInputSource inputSource, PlatformMouseEvent::CanInitiateDrag canInitiateDrag, WebMouseEventSyntheticClickType syntheticClickType, PlatformMouseEvent::IsTouch isTouchEvent, WebCore::PointerID pointerId, const String& pointerType, GestureWasCancelled gestureWasCancelled, const DoublePoint& unadjustedMovementDelta, const Vector& coalescedEvents, const Vector& predictedEvents) -#else -WebMouseEvent::WebMouseEvent(WebEvent&& event, WebMouseEventButton button, unsigned short buttons, const DoublePoint& positionInView, const DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, double force, WebEventInputSource inputSource, PlatformMouseEvent::CanInitiateDrag canInitiateDrag, WebMouseEventSyntheticClickType syntheticClickType, WebCore::PointerID pointerId, const String& pointerType, GestureWasCancelled gestureWasCancelled, const DoublePoint& unadjustedMovementDelta, const Vector& coalescedEvents, const Vector& predictedEvents) -#endif - : WebEvent(WTF::move(event)) - , m_button(button) - , m_buttons(buttons) - , m_position(positionInView) - , m_globalPosition(globalPosition) - , m_deltaX(deltaX) - , m_deltaY(deltaY) - , m_deltaZ(deltaZ) - , m_unadjustedMovementDelta(unadjustedMovementDelta) - , m_clickCount(clickCount) -#if PLATFORM(MAC) - , m_eventNumber(eventNumber) - , m_menuTypeForEvent(menuType) -#elif PLATFORM(GTK) - , m_isTouchEvent(isTouchEvent) -#endif - , m_force(force) - , m_inputSource(inputSource) - , m_canInitiateDrag(canInitiateDrag) - , m_syntheticClickType(syntheticClickType) -#if !PLATFORM(MAC) - , m_pointerId(pointerId) - , m_pointerType(pointerType) -#endif - , m_gestureWasCancelled(gestureWasCancelled) - , m_coalescedEvents(coalescedEvents) - , m_predictedEvents(predictedEvents) +WTF_MAKE_TZONE_ALLOCATED_IMPL(WebMouseEvent); + +Ref WebMouseEvent::create(WebEventData&& eventData, WebMouseEventData&& mouseData) +{ + return adoptRef(*new WebMouseEvent(WTF::move(eventData), WTF::move(mouseData))); +} + +Ref WebMouseEvent::create(WebMouseEventInit&& init) +{ + return create(WTF::move(init.event), WTF::move(init.mouse)); +} + +Ref WebMouseEvent::copy() const +{ + Ref copy = create(WebEventData { eventData() }, WebMouseEventData { m_data }); + copy->setPredictedEvents(m_predictedEvents); + return copy; +} + +WebMouseEvent::WebMouseEvent(WebEventData&& eventData, WebMouseEventData&& mouseData) + : WebEvent(WTF::move(eventData)) + , m_data(WTF::move(mouseData)) { ASSERT(isMouseEventType(type())); } diff --git a/Source/WebKit/Shared/WebMouseEvent.h b/Source/WebKit/Shared/WebMouseEvent.h index 10729b775bca..1b51c9b95bdc 100644 --- a/Source/WebKit/Shared/WebMouseEvent.h +++ b/Source/WebKit/Shared/WebMouseEvent.h @@ -62,74 +62,101 @@ enum class WebMouseEventSyntheticClickType : uint8_t { WebMouseEventSyntheticClickType NODELETE syntheticClickType(const WebCore::NavigationAction&); WebCore::SyntheticClickType NODELETE coreSyntheticClickType(WebMouseEventSyntheticClickType); -class WebMouseEvent : public WebEvent { -public: +class WebMouseEvent; + +// Field order matches WebEvent.serialization.in. Predicted events are deliberately absent because +// they are not sent over IPC; WebMouseEvent stores them separately. +struct WebMouseEventData { + WebMouseEventButton button { WebMouseEventButton::None }; + unsigned short buttons { 0 }; + WebCore::DoublePoint position; // Relative to the view. + WebCore::DoublePoint globalPosition; + float deltaX { 0 }; + float deltaY { 0 }; + float deltaZ { 0 }; + int32_t clickCount { 0 }; + double force { 0 }; + WebEventInputSource inputSource { WebEventInputSource::UserDriven }; + WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag { WebCore::PlatformMouseEvent::CanInitiateDrag::Yes }; + WebMouseEventSyntheticClickType syntheticClickType { WebMouseEventSyntheticClickType::NoTap }; #if PLATFORM(MAC) - WebMouseEvent(WebEvent&&, WebMouseEventButton, unsigned short buttons, const WebCore::DoublePoint& positionInView, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, double force, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap, int eventNumber = -1, int menuType = 0, GestureWasCancelled = GestureWasCancelled::No, const WebCore::DoublePoint& unadjustedMovementDelta = { }, const Vector& coalescedEvents = { }, const Vector& predictedEvents = { }); + int32_t eventNumber { -1 }; + int32_t menuTypeForEvent { 0 }; #elif PLATFORM(GTK) - WebMouseEvent(WebEvent&&, WebMouseEventButton, unsigned short buttons, const WebCore::DoublePoint& positionInView, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, double force, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap, WebCore::PlatformMouseEvent::IsTouch m_isTouchEvent = WebCore::PlatformMouseEvent::IsTouch::No, WebCore::PointerID = WebCore::mousePointerID, const String& pointerType = WebCore::mousePointerEventType(), GestureWasCancelled = GestureWasCancelled::No, const WebCore::DoublePoint& unadjustedMovementDelta = { }, const Vector& coalescedEvents = { }, const Vector& predictedEvents = { }); -#else - WebMouseEvent(WebEvent&&, WebMouseEventButton, unsigned short buttons, const WebCore::DoublePoint& positionInView, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, double force, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap, WebCore::PointerID = WebCore::mousePointerID, const String& pointerType = WebCore::mousePointerEventType(), GestureWasCancelled = GestureWasCancelled::No, const WebCore::DoublePoint& unadjustedMovementDelta = { }, const Vector& coalescedEvents = { }, const Vector& predictedEvents = { }); + WebCore::PlatformMouseEvent::IsTouch isTouchEvent { WebCore::PlatformMouseEvent::IsTouch::No }; #endif +#if !PLATFORM(MAC) + WebCore::PointerID pointerId { WebCore::mousePointerID }; + String pointerType { WebCore::mousePointerEventType() }; +#endif + GestureWasCancelled gestureWasCancelled { GestureWasCancelled::No }; + WebCore::DoublePoint unadjustedMovementDelta; + Vector> coalescedEvents; +}; - WebMouseEventButton button() const { return m_button; } - unsigned short buttons() const { return m_buttons; } - const WebCore::DoublePoint& position() const { return m_position; } // Relative to the view. - void setPosition(const WebCore::DoublePoint& position) { m_position = position; } - const WebCore::DoublePoint& globalPosition() const LIFETIME_BOUND { return m_globalPosition; } - float deltaX() const { return m_deltaX; } - float deltaY() const { return m_deltaY; } - float deltaZ() const { return m_deltaZ; } - int32_t clickCount() const { return m_clickCount; } +struct WebMouseEventInit { + WebEventData event; + WebMouseEventData mouse; +}; + +class WebMouseEvent : public WebEvent { + WTF_MAKE_TZONE_ALLOCATED(WebMouseEvent); +public: + static Ref create(WebEventData&&, WebMouseEventData&&); + static Ref create(WebMouseEventInit&&); + + // Callers that mutate an event they did not create must copy first, since events are now shared + // rather than copied by value. Shallow: nothing mutates the coalesced or predicted events. + Ref copy() const; + + WebMouseEventButton button() const { return m_data.button; } + unsigned short buttons() const { return m_data.buttons; } + const WebCore::DoublePoint& position() const LIFETIME_BOUND { return m_data.position; } // Relative to the view. + void setPosition(const WebCore::DoublePoint& position) { m_data.position = position; } + const WebCore::DoublePoint& globalPosition() const LIFETIME_BOUND { return m_data.globalPosition; } + float deltaX() const { return m_data.deltaX; } + float deltaY() const { return m_data.deltaY; } + float deltaZ() const { return m_data.deltaZ; } + int32_t clickCount() const { return m_data.clickCount; } #if PLATFORM(MAC) - int32_t eventNumber() const { return m_eventNumber; } - int32_t menuTypeForEvent() const { return m_menuTypeForEvent; } + int32_t eventNumber() const { return m_data.eventNumber; } + int32_t menuTypeForEvent() const { return m_data.menuTypeForEvent; } #elif PLATFORM(GTK) - WebCore::PlatformMouseEvent::IsTouch isTouchEvent() const { return m_isTouchEvent; } + WebCore::PlatformMouseEvent::IsTouch isTouchEvent() const { return m_data.isTouchEvent; } #endif - double force() const { return m_force; } - WebEventInputSource inputSource() const { return m_inputSource; } - WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag() const { return m_canInitiateDrag; } - WebMouseEventSyntheticClickType syntheticClickType() const { return m_syntheticClickType; } - WebCore::PointerID pointerId() const { return m_pointerId; } - const String& pointerType() const LIFETIME_BOUND { return m_pointerType; } - GestureWasCancelled gestureWasCancelled() const { return m_gestureWasCancelled; } + double force() const { return m_data.force; } + WebEventInputSource inputSource() const { return m_data.inputSource; } + WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag() const { return m_data.canInitiateDrag; } + WebMouseEventSyntheticClickType syntheticClickType() const { return m_data.syntheticClickType; } +#if PLATFORM(MAC) + // No constructor on this platform takes these, so the defaults are the only possible values. + WebCore::PointerID pointerId() const { return WebCore::mousePointerID; } + const String& pointerType() const LIFETIME_BOUND { return WebCore::mousePointerEventType(); } +#else + WebCore::PointerID pointerId() const { return m_data.pointerId; } + const String& pointerType() const LIFETIME_BOUND { return m_data.pointerType; } +#endif + GestureWasCancelled gestureWasCancelled() const { return m_data.gestureWasCancelled; } // Unaccelerated pointer movement - const WebCore::DoublePoint& unadjustedMovementDelta() const LIFETIME_BOUND { return m_unadjustedMovementDelta; } + const WebCore::DoublePoint& unadjustedMovementDelta() const LIFETIME_BOUND { return m_data.unadjustedMovementDelta; } + + void setCoalescedEvents(const Vector>& coalescedEvents) { m_data.coalescedEvents = coalescedEvents; } + const Vector>& coalescedEvents() const LIFETIME_BOUND { return m_data.coalescedEvents; } - void setCoalescedEvents(const Vector& coalescedEvents) { m_coalescedEvents = coalescedEvents; } - Vector coalescedEvents() const { return m_coalescedEvents; } + void setPredictedEvents(const Vector>& predictedEvents) { m_predictedEvents = predictedEvents; } + const Vector>& predictedEvents() const LIFETIME_BOUND { return m_predictedEvents; } - void setPredictedEvents(const Vector& predictedEvents) { m_predictedEvents = predictedEvents; } - Vector predictedEvents() const { return m_predictedEvents; } + const WebMouseEventData& mouseData() const LIFETIME_BOUND { return m_data; } static bool NODELETE isMouseEventType(WebEventType); +protected: + WebMouseEvent(WebEventData&&, WebMouseEventData&&); + private: - WebMouseEventButton m_button { WebMouseEventButton::None }; - unsigned short m_buttons { 0 }; - WebCore::DoublePoint m_position; // Relative to the view. - WebCore::DoublePoint m_globalPosition; - float m_deltaX { 0 }; - float m_deltaY { 0 }; - float m_deltaZ { 0 }; - WebCore::DoublePoint m_unadjustedMovementDelta; - int32_t m_clickCount { 0 }; -#if PLATFORM(MAC) - int32_t m_eventNumber { -1 }; - int32_t m_menuTypeForEvent { 0 }; -#elif PLATFORM(GTK) - WebCore::PlatformMouseEvent::IsTouch m_isTouchEvent { WebCore::PlatformMouseEvent::IsTouch::No }; -#endif - double m_force { 0 }; - WebEventInputSource m_inputSource { WebEventInputSource::UserDriven }; - WebCore::PlatformMouseEvent::CanInitiateDrag m_canInitiateDrag { WebCore::PlatformMouseEvent::CanInitiateDrag::Yes }; - WebMouseEventSyntheticClickType m_syntheticClickType { WebMouseEventSyntheticClickType::NoTap }; - WebCore::PointerID m_pointerId { WebCore::mousePointerID }; - String m_pointerType { WebCore::mousePointerEventType() }; - GestureWasCancelled m_gestureWasCancelled { GestureWasCancelled::No }; - Vector m_coalescedEvents; - Vector m_predictedEvents; + WebMouseEventData m_data; + // Not sent over IPC. See WebMouseEventData. + Vector> m_predictedEvents; }; } // namespace WebKit diff --git a/Source/WebKit/Shared/WebTouchEvent.cpp b/Source/WebKit/Shared/WebTouchEvent.cpp index 9bef529c796f..1af6065e0fa6 100644 --- a/Source/WebKit/Shared/WebTouchEvent.cpp +++ b/Source/WebKit/Shared/WebTouchEvent.cpp @@ -30,39 +30,55 @@ #include "ArgumentCoders.h" #include +#include namespace WebKit { -#if !PLATFORM(IOS_FAMILY) +WTF_MAKE_TZONE_ALLOCATED_IMPL(WebTouchEvent); -WebTouchEvent::WebTouchEvent(WebEvent&& event, Vector&& touchPoints, Vector&& coalescedEvents, Vector&& predictedEvents) - : WebEvent(WTF::move(event)) - , m_touchPoints(WTF::move(touchPoints)) - , m_coalescedEvents(WTF::move(coalescedEvents)) - , m_predictedEvents(WTF::move(predictedEvents)) +Ref WebTouchEvent::create(WebEventData&& eventData, WebTouchEventData&& touchData) +{ + return adoptRef(*new WebTouchEvent(WTF::move(eventData), WTF::move(touchData))); +} + +Ref WebTouchEvent::create(WebTouchEventInit&& init) +{ + return create(WTF::move(init.event), WTF::move(init.touch)); +} + +WebTouchEvent::WebTouchEvent(WebEventData&& eventData, WebTouchEventData&& touchData) + : WebEvent(WTF::move(eventData)) + , m_data(WTF::move(touchData)) { ASSERT(isTouchEventType(type())); } +Ref WebTouchEvent::copy() const +{ + auto data = m_data; + data.coalescedEvents = WTF::map(m_data.coalescedEvents, [](auto& event) { return event->copy(); }); + data.predictedEvents = WTF::map(m_data.predictedEvents, [](auto& event) { return event->copy(); }); + return create(WebEventData { eventData() }, WTF::move(data)); +} + bool WebTouchEvent::isTouchEventType(WebEventType type) { return type == WebEventType::TouchStart || type == WebEventType::TouchMove || type == WebEventType::TouchEnd || type == WebEventType::TouchCancel; } -#endif // !PLATFORM(IOS_FAMILY) #if ENABLE(IOS_TOUCH_EVENTS) void WebTouchEvent::transformToRemoteFrameCoordinates(const WebCore::RemoteFrameGeometryTransformer& transformer) { ASSERT(!std::exchange(m_hasTransformedToRemoteFrameCoordinates, true)); - m_position = transformer.transformRootViewPointToRemoteFrameCoordinates(m_position); - for (auto& touchPoint : m_touchPoints) + m_data.position = transformer.transformRootViewPointToRemoteFrameCoordinates(m_data.position); + for (auto& touchPoint : m_data.touchPoints) touchPoint.transformToRemoteFrameCoordinates(transformer); - for (auto& event : m_coalescedEvents) - event.transformToRemoteFrameCoordinates(transformer); - for (auto& event : m_predictedEvents) - event.transformToRemoteFrameCoordinates(transformer); + for (Ref event : m_data.coalescedEvents) + event->transformToRemoteFrameCoordinates(transformer); + for (Ref event : m_data.predictedEvents) + event->transformToRemoteFrameCoordinates(transformer); } void WebPlatformTouchPoint::transformToRemoteFrameCoordinates(const WebCore::RemoteFrameGeometryTransformer& transformer) diff --git a/Source/WebKit/Shared/WebTouchEvent.h b/Source/WebKit/Shared/WebTouchEvent.h index 2267f278b54e..ff909e8d81eb 100644 --- a/Source/WebKit/Shared/WebTouchEvent.h +++ b/Source/WebKit/Shared/WebTouchEvent.h @@ -130,57 +130,68 @@ class WebPlatformTouchPoint { #endif }; +class WebTouchEvent; + +// Field order matches WebEvent.serialization.in. +struct WebTouchEventData { + Vector touchPoints; + Vector> coalescedEvents; + Vector> predictedEvents; + WebCore::DoublePoint position; + bool isPotentialTap { false }; + bool isGesture { false }; + float gestureScale { 0 }; + float gestureRotation { 0 }; + bool canPreventNativeGestures { true }; +}; + +struct WebTouchEventInit { + WebEventData event; + WebTouchEventData touch; +}; + class WebTouchEvent : public WebEvent { + WTF_MAKE_TZONE_ALLOCATED(WebTouchEvent); public: - WebTouchEvent(WebEvent&& event, const Vector& touchPoints, const Vector& coalescedEvents, const Vector& predictedEvents, WebCore::DoublePoint position, bool isPotentialTap, bool isGesture, float gestureScale, float gestureRotation, bool canPreventNativeGestures = true) - : WebEvent(WTF::move(event)) - , m_touchPoints(touchPoints) - , m_coalescedEvents(coalescedEvents) - , m_predictedEvents(predictedEvents) - , m_position(position) - , m_canPreventNativeGestures(canPreventNativeGestures) - , m_isPotentialTap(isPotentialTap) - , m_isGesture(isGesture) - , m_gestureScale(gestureScale) - , m_gestureRotation(gestureRotation) - { - ASSERT(type() == WebEventType::TouchStart || type() == WebEventType::TouchMove || type() == WebEventType::TouchEnd || type() == WebEventType::TouchCancel); - } + static Ref create(WebEventData&&, WebTouchEventData&&); + static Ref create(WebTouchEventInit&&); + + // Deep copy, including the coalesced and predicted events. Callers that mutate an event they + // did not create must copy first, since events are now shared rather than copied by value. + Ref copy() const; - const Vector& touchPoints() const LIFETIME_BOUND { return m_touchPoints; } + const Vector& touchPoints() const LIFETIME_BOUND { return m_data.touchPoints; } - const Vector& coalescedEvents() const LIFETIME_BOUND { return m_coalescedEvents; } - void setCoalescedEvents(const Vector& coalescedEvents) { m_coalescedEvents = coalescedEvents; } + const Vector>& coalescedEvents() const LIFETIME_BOUND { return m_data.coalescedEvents; } + void setCoalescedEvents(const Vector>& coalescedEvents) { m_data.coalescedEvents = coalescedEvents; } - const Vector& predictedEvents() const LIFETIME_BOUND { return m_predictedEvents; } - void setPredictedEvents(const Vector& predictedEvents) { m_predictedEvents = predictedEvents; } + const Vector>& predictedEvents() const LIFETIME_BOUND { return m_data.predictedEvents; } + void setPredictedEvents(const Vector>& predictedEvents) { m_data.predictedEvents = predictedEvents; } - WebCore::DoublePoint position() const { return m_position; } + WebCore::DoublePoint position() const { return m_data.position; } void transformToRemoteFrameCoordinates(const WebCore::RemoteFrameGeometryTransformer&); - bool isPotentialTap() const { return m_isPotentialTap; } + bool isPotentialTap() const { return m_data.isPotentialTap; } - bool isGesture() const { return m_isGesture; } - float gestureScale() const { return m_gestureScale; } - float gestureRotation() const { return m_gestureRotation; } + bool isGesture() const { return m_data.isGesture; } + float gestureScale() const { return m_data.gestureScale; } + float gestureRotation() const { return m_data.gestureRotation; } - bool canPreventNativeGestures() const { return m_canPreventNativeGestures; } - void setCanPreventNativeGestures(bool canPreventNativeGestures) { m_canPreventNativeGestures = canPreventNativeGestures; } + bool canPreventNativeGestures() const { return m_data.canPreventNativeGestures; } + void setCanPreventNativeGestures(bool canPreventNativeGestures) { m_data.canPreventNativeGestures = canPreventNativeGestures; } bool allTouchPointsAreReleased() const; - + + const WebTouchEventData& touchData() const LIFETIME_BOUND { return m_data; } + +protected: + WebTouchEvent(WebEventData&&, WebTouchEventData&&); + private: - Vector m_touchPoints; - Vector m_coalescedEvents; - Vector m_predictedEvents; + static bool isTouchEventType(WebEventType); - WebCore::DoublePoint m_position; - bool m_canPreventNativeGestures { false }; - bool m_isPotentialTap { false }; - bool m_isGesture { false }; - float m_gestureScale { 0 }; - float m_gestureRotation { 0 }; + WebTouchEventData m_data; #if ASSERT_ENABLED bool m_hasTransformedToRemoteFrameCoordinates { false }; #endif @@ -226,15 +237,34 @@ class WebPlatformTouchPoint { float m_force; }; +class WebTouchEvent; + +// Field order matches WebEvent.serialization.in. +struct WebTouchEventData { + Vector touchPoints; + Vector> coalescedEvents; + Vector> predictedEvents; +}; + +struct WebTouchEventInit { + WebEventData event; + WebTouchEventData touch; +}; + class WebTouchEvent : public WebEvent { + WTF_MAKE_TZONE_ALLOCATED(WebTouchEvent); public: - WebTouchEvent(WebEvent&&, Vector&&, Vector&&, Vector&&); + static Ref create(WebEventData&&, WebTouchEventData&&); + static Ref create(WebTouchEventInit&&); + + // See the IOS_FAMILY variant. + Ref copy() const; - const Vector& touchPoints() const LIFETIME_BOUND { return m_touchPoints; } + const Vector& touchPoints() const LIFETIME_BOUND { return m_data.touchPoints; } - const Vector& coalescedEvents() const LIFETIME_BOUND { return m_coalescedEvents; } + const Vector>& coalescedEvents() const LIFETIME_BOUND { return m_data.coalescedEvents; } - const Vector& predictedEvents() const LIFETIME_BOUND { return m_predictedEvents; } + const Vector>& predictedEvents() const LIFETIME_BOUND { return m_data.predictedEvents; } bool allTouchPointsAreReleased() const; @@ -242,12 +272,15 @@ class WebTouchEvent : public WebEvent { virtual bool isNativeWebTouchEvent() const { return false; } #endif + const WebTouchEventData& touchData() const LIFETIME_BOUND { return m_data; } + +protected: + WebTouchEvent(WebEventData&&, WebTouchEventData&&); + private: static bool isTouchEventType(WebEventType); - Vector m_touchPoints; - Vector m_coalescedEvents; - Vector m_predictedEvents; + WebTouchEventData m_data; }; #endif // PLATFORM(IOS_FAMILY) diff --git a/Source/WebKit/Shared/WebWheelEvent.cpp b/Source/WebKit/Shared/WebWheelEvent.cpp index bff2210aedba..5602ecc825c9 100644 --- a/Source/WebKit/Shared/WebWheelEvent.cpp +++ b/Source/WebKit/Shared/WebWheelEvent.cpp @@ -25,60 +25,36 @@ #include "config.h" #include "WebWheelEvent.h" +#include #include namespace WebKit { using namespace WebCore; -WebWheelEvent::WebWheelEvent(WebEvent&& event, const IntPoint& position, const IntPoint& globalPosition, const FloatSize& delta, const FloatSize& wheelTicks, Granularity granularity) - : WebEvent(WTF::move(event)) - , m_position(position) - , m_globalPosition(globalPosition) - , m_delta(delta) - , m_wheelTicks(wheelTicks) - , m_granularity(granularity) +WTF_MAKE_TZONE_ALLOCATED_IMPL(WebWheelEvent); + +Ref WebWheelEvent::create(WebEventData&& eventData, WebWheelEventData&& wheelData) { - ASSERT(isWheelEventType(type())); + return adoptRef(*new WebWheelEvent(WTF::move(eventData), WTF::move(wheelData))); } -#if PLATFORM(COCOA) -WebWheelEvent::WebWheelEvent(WebEvent&& event, const IntPoint& position, const IntPoint& globalPosition, const FloatSize& delta, const FloatSize& wheelTicks, Granularity granularity, bool directionInvertedFromDevice, Phase phase, Phase momentumPhase, bool hasPreciseScrollingDeltas, uint32_t scrollCount, const WebCore::FloatSize& unacceleratedScrollingDelta, MonotonicTime ioHIDEventTimestamp, std::optional rawPlatformDelta, MomentumEndType momentumEndType, WebEventInputSource inputSource, float momentumFastScrollMultiplier) - : WebEvent(WTF::move(event)) - , m_position(position) - , m_globalPosition(globalPosition) - , m_delta(delta) - , m_wheelTicks(wheelTicks) - , m_granularity(granularity) - , m_phase(phase) - , m_momentumPhase(momentumPhase) - , m_momentumEndType(momentumEndType) - , m_directionInvertedFromDevice(directionInvertedFromDevice) - , m_hasPreciseScrollingDeltas(hasPreciseScrollingDeltas) - , m_ioHIDEventTimestamp(ioHIDEventTimestamp) - , m_rawPlatformDelta(rawPlatformDelta) - , m_scrollCount(scrollCount) - , m_unacceleratedScrollingDelta(unacceleratedScrollingDelta) - , m_inputSource(inputSource) - , m_momentumFastScrollMultiplier(momentumFastScrollMultiplier) +Ref WebWheelEvent::create(WebWheelEventInit&& init) { - ASSERT(isWheelEventType(type())); + return create(WTF::move(init.event), WTF::move(init.wheel)); } -#elif PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) -WebWheelEvent::WebWheelEvent(WebEvent&& event, const IntPoint& position, const IntPoint& globalPosition, const FloatSize& delta, const FloatSize& wheelTicks, Granularity granularity, Phase phase, Phase momentumPhase, bool hasPreciseScrollingDeltas) - : WebEvent(WTF::move(event)) - , m_position(position) - , m_globalPosition(globalPosition) - , m_delta(delta) - , m_wheelTicks(wheelTicks) - , m_granularity(granularity) - , m_phase(phase) - , m_momentumPhase(momentumPhase) - , m_hasPreciseScrollingDeltas(hasPreciseScrollingDeltas) + +Ref WebWheelEvent::copy() const +{ + return create(WebEventData { eventData() }, WebWheelEventData { m_data }); +} + +WebWheelEvent::WebWheelEvent(WebEventData&& eventData, WebWheelEventData&& wheelData) + : WebEvent(WTF::move(eventData)) + , m_data(WTF::move(wheelData)) { ASSERT(isWheelEventType(type())); } -#endif bool WebWheelEvent::isWheelEventType(WebEventType type) { diff --git a/Source/WebKit/Shared/WebWheelEvent.h b/Source/WebKit/Shared/WebWheelEvent.h index 4c398654b08f..e65165b81fa5 100644 --- a/Source/WebKit/Shared/WebWheelEvent.h +++ b/Source/WebKit/Shared/WebWheelEvent.h @@ -37,78 +37,109 @@ class TextStream; namespace WebKit { +// Moved out of WebWheelEvent so that WebWheelEventData can name them. WebWheelEvent keeps aliases, +// so existing WebWheelEvent::Granularity / ::MomentumEndType spellings continue to work. +enum class WebWheelEventGranularity : uint8_t { + ScrollByPageWheelEvent, + ScrollByPixelWheelEvent +}; + +enum class WebWheelEventMomentumEndType : uint8_t { + Unknown, + Interrupted, + Natural, +}; + +// Field order matches WebEvent.serialization.in. Fields absent on a platform are ones no +// constructor there sets, so the corresponding accessors return constants. +struct WebWheelEventData { + WebCore::IntPoint position; + WebCore::IntPoint globalPosition; + WebCore::FloatSize delta; + WebCore::FloatSize wheelTicks; + WebWheelEventGranularity granularity { WebWheelEventGranularity::ScrollByPageWheelEvent }; +#if PLATFORM(COCOA) + bool directionInvertedFromDevice { false }; +#endif +#if PLATFORM(COCOA) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) + WebEventPhase phase { WebEventPhase::None }; + WebEventPhase momentumPhase { WebEventPhase::None }; + bool hasPreciseScrollingDeltas { false }; +#endif +#if PLATFORM(COCOA) + uint32_t scrollCount { 0 }; + WebCore::FloatSize unacceleratedScrollingDelta; + MonotonicTime ioHIDEventTimestamp; + std::optional rawPlatformDelta; + WebWheelEventMomentumEndType momentumEndType { WebWheelEventMomentumEndType::Unknown }; + WebEventInputSource inputSource { WebEventInputSource::UserDriven }; + float momentumFastScrollMultiplier { 1 }; +#endif +}; + +struct WebWheelEventInit { + WebEventData event; + WebWheelEventData wheel; +}; + class WebWheelEvent : public WebEvent { + WTF_MAKE_TZONE_ALLOCATED(WebWheelEvent); public: - enum class Granularity : uint8_t { - ScrollByPageWheelEvent, - ScrollByPixelWheelEvent - }; - + using Granularity = WebWheelEventGranularity; using Phase = WebEventPhase; + using MomentumEndType = WebWheelEventMomentumEndType; - enum class MomentumEndType : uint8_t { - Unknown, - Interrupted, - Natural, - }; + static Ref create(WebEventData&&, WebWheelEventData&&); + static Ref create(WebWheelEventInit&&); - WebWheelEvent(WebEvent&&, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Granularity); + // Callers that mutate an event they did not create must copy first, since events are now shared + // rather than copied by value. + Ref copy() const; + + const WebCore::IntPoint position() const { return m_data.position; } + void setPosition(WebCore::IntPoint position) { m_data.position = position; } + const WebCore::IntPoint globalPosition() const { return m_data.globalPosition; } + const WebCore::FloatSize delta() const { return m_data.delta; } + const WebCore::FloatSize wheelTicks() const { return m_data.wheelTicks; } + Granularity granularity() const { return m_data.granularity; } #if PLATFORM(COCOA) - WebWheelEvent(WebEvent&&, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Granularity, bool directionInvertedFromDevice, Phase, Phase momentumPhase, bool hasPreciseScrollingDeltas, uint32_t scrollCount, const WebCore::FloatSize& unacceleratedScrollingDelta, MonotonicTime ioHIDEventTimestamp, std::optional rawPlatformDelta, MomentumEndType, WebEventInputSource = WebEventInputSource::UserDriven, float momentumFastScrollMultiplier = 1); -#elif PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - WebWheelEvent(WebEvent&&, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Granularity, Phase, Phase momentumPhase, bool hasPreciseScrollingDeltas); + bool directionInvertedFromDevice() const { return m_data.directionInvertedFromDevice; } + MomentumEndType momentumEndType() const { return m_data.momentumEndType; } +#else + // No constructor on this platform takes these, so the defaults are the only possible values. + bool directionInvertedFromDevice() const { return false; } + MomentumEndType momentumEndType() const { return MomentumEndType::Unknown; } #endif - - const WebCore::IntPoint position() const { return m_position; } - void setPosition(WebCore::IntPoint position) { m_position = position; } - const WebCore::IntPoint globalPosition() const { return m_globalPosition; } - const WebCore::FloatSize delta() const { return m_delta; } - const WebCore::FloatSize wheelTicks() const { return m_wheelTicks; } - Granularity granularity() const { return m_granularity; } - bool directionInvertedFromDevice() const { return m_directionInvertedFromDevice; } - Phase phase() const { return m_phase; } - Phase momentumPhase() const { return m_momentumPhase; } - MomentumEndType momentumEndType() const { return m_momentumEndType; } #if PLATFORM(COCOA) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - bool hasPreciseScrollingDeltas() const { return m_hasPreciseScrollingDeltas; } + Phase phase() const { return m_data.phase; } + Phase momentumPhase() const { return m_data.momentumPhase; } + bool hasPreciseScrollingDeltas() const { return m_data.hasPreciseScrollingDeltas; } +#else + Phase phase() const { return Phase::None; } + Phase momentumPhase() const { return Phase::None; } #endif #if PLATFORM(COCOA) - MonotonicTime ioHIDEventTimestamp() const { return m_ioHIDEventTimestamp; } - std::optional rawPlatformDelta() const { return m_rawPlatformDelta; } - void setRawPlatformDelta(std::optional&& delta) { m_rawPlatformDelta = WTF::move(delta); } - uint32_t scrollCount() const { return m_scrollCount; } - const WebCore::FloatSize& unacceleratedScrollingDelta() const LIFETIME_BOUND { return m_unacceleratedScrollingDelta; } - WebEventInputSource inputSource() const { return m_inputSource; } - float momentumFastScrollMultiplier() const { return m_momentumFastScrollMultiplier; } - void setMomentumFastScrollMultiplier(float multiplier) { m_momentumFastScrollMultiplier = multiplier; } + MonotonicTime ioHIDEventTimestamp() const { return m_data.ioHIDEventTimestamp; } + std::optional rawPlatformDelta() const { return m_data.rawPlatformDelta; } + void setRawPlatformDelta(std::optional&& delta) { m_data.rawPlatformDelta = WTF::move(delta); } + uint32_t scrollCount() const { return m_data.scrollCount; } + const WebCore::FloatSize& unacceleratedScrollingDelta() const LIFETIME_BOUND { return m_data.unacceleratedScrollingDelta; } + WebEventInputSource inputSource() const { return m_data.inputSource; } + float momentumFastScrollMultiplier() const { return m_data.momentumFastScrollMultiplier; } + void setMomentumFastScrollMultiplier(float multiplier) { m_data.momentumFastScrollMultiplier = multiplier; } #endif // PLATFORM(COCOA) bool isMomentumEvent() const { return momentumPhase() != Phase::None && momentumPhase() != Phase::WillBegin; } + const WebWheelEventData& wheelData() const LIFETIME_BOUND { return m_data; } + static bool NODELETE isWheelEventType(WebEventType); +protected: + WebWheelEvent(WebEventData&&, WebWheelEventData&&); + private: - WebCore::IntPoint m_position; - WebCore::IntPoint m_globalPosition; - WebCore::FloatSize m_delta; - WebCore::FloatSize m_wheelTicks; - Granularity m_granularity { Granularity::ScrollByPageWheelEvent }; - Phase m_phase { Phase::None }; - Phase m_momentumPhase { Phase::None }; - - MomentumEndType m_momentumEndType { MomentumEndType::Unknown }; - bool m_directionInvertedFromDevice { false }; -#if PLATFORM(COCOA) || PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - bool m_hasPreciseScrollingDeltas { false }; -#endif -#if PLATFORM(COCOA) - MonotonicTime m_ioHIDEventTimestamp; - std::optional m_rawPlatformDelta; - uint32_t m_scrollCount { 0 }; - WebCore::FloatSize m_unacceleratedScrollingDelta; - WebEventInputSource m_inputSource { WebEventInputSource::UserDriven }; - float m_momentumFastScrollMultiplier { 1 }; -#endif // PLATFORM(COCOA) + WebWheelEventData m_data; }; WTF::TextStream& operator<<(WTF::TextStream&, WebWheelEvent::Granularity); diff --git a/Source/WebKit/Shared/WebWheelEventCoalescer.cpp b/Source/WebKit/Shared/WebWheelEventCoalescer.cpp index 95795eae1b4b..2f15e83ad77b 100644 --- a/Source/WebKit/Shared/WebWheelEventCoalescer.cpp +++ b/Source/WebKit/Shared/WebWheelEventCoalescer.cpp @@ -37,14 +37,6 @@ namespace WebKit { // Represents the number of wheel events we can hold in the queue before we start pushing them preemptively. constexpr unsigned wheelEventQueueSizeThreshold = 10; -#if !LOG_DISABLED -static TextStream& operator<<(TextStream& ts, const NativeWebWheelEvent& nativeWheelEvent) -{ - ts << platform(nativeWheelEvent); - return ts; -} -#endif - WTF_MAKE_TZONE_ALLOCATED_IMPL(WebWheelEventCoalescer); bool WebWheelEventCoalescer::canCoalesce(const WebWheelEvent& a, const WebWheelEvent& b) @@ -71,7 +63,7 @@ bool WebWheelEventCoalescer::canCoalesce(const WebWheelEvent& a, const WebWheelE return true; } -WebWheelEvent WebWheelEventCoalescer::coalesce(const WebWheelEvent& a, const WebWheelEvent& b) +Ref WebWheelEventCoalescer::coalesce(const WebWheelEvent& a, const WebWheelEvent& b) { ASSERT(canCoalesce(a, b)); @@ -84,13 +76,44 @@ WebWheelEvent WebWheelEventCoalescer::coalesce(const WebWheelEvent& a, const Web if (a.rawPlatformDelta() && b.rawPlatformDelta()) mergedRawPlatformScrollingDelta = a.rawPlatformDelta().value() + b.rawPlatformDelta().value(); - auto event = WebWheelEvent({ WebEventType::Wheel, b.modifiers(), b.timestamp() }, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.granularity(), b.directionInvertedFromDevice(), b.phase(), b.momentumPhase(), b.hasPreciseScrollingDeltas(), b.scrollCount(), mergedUnacceleratedScrollingDelta, b.ioHIDEventTimestamp(), mergedRawPlatformScrollingDelta, b.momentumEndType(), b.inputSource()); + auto wheelData = WebWheelEventData { + .position = b.position(), + .globalPosition = b.globalPosition(), + .delta = mergedDelta, + .wheelTicks = mergedWheelTicks, + .granularity = b.granularity(), + .directionInvertedFromDevice = b.directionInvertedFromDevice(), + .phase = b.phase(), + .momentumPhase = b.momentumPhase(), + .hasPreciseScrollingDeltas = b.hasPreciseScrollingDeltas(), + .scrollCount = b.scrollCount(), + .unacceleratedScrollingDelta = mergedUnacceleratedScrollingDelta, + .ioHIDEventTimestamp = b.ioHIDEventTimestamp(), + .rawPlatformDelta = mergedRawPlatformScrollingDelta, + .momentumEndType = b.momentumEndType(), + .inputSource = b.inputSource(), + }; #elif PLATFORM(GTK) || USE(LIBWPE) || ENABLE(WPE_PLATFORM) - auto event = WebWheelEvent({ WebEventType::Wheel, b.modifiers(), b.timestamp() }, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.granularity(), b.phase(), b.momentumPhase(), b.hasPreciseScrollingDeltas()); + auto wheelData = WebWheelEventData { + .position = b.position(), + .globalPosition = b.globalPosition(), + .delta = mergedDelta, + .wheelTicks = mergedWheelTicks, + .granularity = b.granularity(), + .phase = b.phase(), + .momentumPhase = b.momentumPhase(), + .hasPreciseScrollingDeltas = b.hasPreciseScrollingDeltas(), + }; #else - auto event = WebWheelEvent({ WebEventType::Wheel, b.modifiers(), b.timestamp() }, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.granularity()); + auto wheelData = WebWheelEventData { + .position = b.position(), + .globalPosition = b.globalPosition(), + .delta = mergedDelta, + .wheelTicks = mergedWheelTicks, + .granularity = b.granularity(), + }; #endif - return event; + return WebWheelEvent::create({ WebEventType::Wheel, b.modifiers(), b.timestamp() }, WTF::move(wheelData)); } bool WebWheelEventCoalescer::shouldDispatchEventNow(const WebWheelEvent& event) const @@ -113,38 +136,40 @@ bool WebWheelEventCoalescer::shouldDispatchEventNow(const WebWheelEvent& event) return m_wheelEventQueue.size() >= wheelEventQueueSizeThreshold; } -std::optional WebWheelEventCoalescer::nextEventToDispatch() +RefPtr WebWheelEventCoalescer::nextEventToDispatch() { if (m_wheelEventQueue.isEmpty()) - return std::nullopt; + return nullptr; - auto coalescedNativeEvent = m_wheelEventQueue.takeFirst(); + Ref coalescedNativeEvent = m_wheelEventQueue.takeFirst(); auto coalescedSequence = makeUnique(); coalescedSequence->append(coalescedNativeEvent); - auto coalescedWebEvent = WebWheelEvent { coalescedNativeEvent }; + RefPtr coalescedWebEvent = coalescedNativeEvent.ptr(); - while (!m_wheelEventQueue.isEmpty() && canCoalesce(coalescedWebEvent, m_wheelEventQueue.first())) { - auto firstEvent = m_wheelEventQueue.takeFirst(); + while (!m_wheelEventQueue.isEmpty() && canCoalesce(*coalescedWebEvent, m_wheelEventQueue.first())) { + Ref firstEvent = m_wheelEventQueue.takeFirst(); coalescedSequence->append(firstEvent); - SUPPRESS_UNCHECKED_ARG coalescedWebEvent = coalesce(coalescedWebEvent, WebWheelEvent { firstEvent }); + coalescedWebEvent = coalesce(*coalescedWebEvent, firstEvent); } #if !LOG_DISABLED - if (coalescedSequence->size() > 1) - LOG_WITH_STREAM(WheelEvents, stream << "WebWheelEventCoalescer::wheelEventWithCoalescing coalesced " << *coalescedSequence << " into " << platform(coalescedWebEvent)); + if (coalescedSequence->size() > 1) { + auto platformEvents = WTF::map(*coalescedSequence, [](auto& event) { return platform(event.get()); }); + LOG_WITH_STREAM(WheelEvents, stream << "WebWheelEventCoalescer::wheelEventWithCoalescing coalesced " << platformEvents << " into " << platform(*coalescedWebEvent)); + } #endif m_eventsBeingProcessed.append(WTF::move(coalescedSequence)); return coalescedWebEvent; } -bool WebWheelEventCoalescer::shouldDispatchEvent(const NativeWebWheelEvent& event) +bool WebWheelEventCoalescer::shouldDispatchEvent(Ref&& event) { - LOG_WITH_STREAM(WheelEvents, stream << "WebWheelEventCoalescer::shouldDispatchEvent " << platform(event) << " (" << m_wheelEventQueue.size() << " events in the queue, " << m_eventsBeingProcessed.size() << " event sequences being processed)"); + LOG_WITH_STREAM(WheelEvents, stream << "WebWheelEventCoalescer::shouldDispatchEvent " << platform(event.get()) << " (" << m_wheelEventQueue.size() << " events in the queue, " << m_eventsBeingProcessed.size() << " event sequences being processed)"); - m_wheelEventQueue.append(event); + m_wheelEventQueue.append(WTF::move(event)); if (!m_eventsBeingProcessed.isEmpty()) { if (!shouldDispatchEventNow(m_wheelEventQueue.last())) { @@ -158,13 +183,13 @@ bool WebWheelEventCoalescer::shouldDispatchEvent(const NativeWebWheelEvent& even return true; } -std::optional WebWheelEventCoalescer::takeOldestEventBeingProcessed() +RefPtr WebWheelEventCoalescer::takeOldestEventBeingProcessed() { if (m_eventsBeingProcessed.isEmpty()) - return { }; + return nullptr; auto oldestSequence = m_eventsBeingProcessed.takeFirst(); - return oldestSequence->last(); + return oldestSequence->last().ptr(); } void WebWheelEventCoalescer::clear() diff --git a/Source/WebKit/Shared/WebWheelEventCoalescer.h b/Source/WebKit/Shared/WebWheelEventCoalescer.h index ad0b6a9c71f8..cf8cd9a59bf4 100644 --- a/Source/WebKit/Shared/WebWheelEventCoalescer.h +++ b/Source/WebKit/Shared/WebWheelEventCoalescer.h @@ -35,24 +35,24 @@ class WebWheelEventCoalescer { WTF_MAKE_TZONE_ALLOCATED(WebWheelEventCoalescer); public: // If this returns true, use nextEventToDispatch() to get the event to dispatch. - bool shouldDispatchEvent(const NativeWebWheelEvent&); - std::optional nextEventToDispatch(); + bool shouldDispatchEvent(Ref&&); + RefPtr nextEventToDispatch(); - std::optional takeOldestEventBeingProcessed(); + RefPtr takeOldestEventBeingProcessed(); bool hasEventsBeingProcessed() const { return !m_eventsBeingProcessed.isEmpty(); } void clear(); private: - using CoalescedEventSequence = Vector; + using CoalescedEventSequence = Vector>; static bool NODELETE canCoalesce(const WebWheelEvent&, const WebWheelEvent&); - static WebWheelEvent coalesce(const WebWheelEvent&, const WebWheelEvent&); + static Ref coalesce(const WebWheelEvent&, const WebWheelEvent&); bool NODELETE shouldDispatchEventNow(const WebWheelEvent&) const; - Deque m_wheelEventQueue; + Deque, 2> m_wheelEventQueue; Deque> m_eventsBeingProcessed; }; diff --git a/Source/WebKit/Shared/gtk/NativeWebKeyboardEventGtk.cpp b/Source/WebKit/Shared/gtk/NativeWebKeyboardEventGtk.cpp index 6091f843195a..0d98f28bf829 100644 --- a/Source/WebKit/Shared/gtk/NativeWebKeyboardEventGtk.cpp +++ b/Source/WebKit/Shared/gtk/NativeWebKeyboardEventGtk.cpp @@ -30,6 +30,7 @@ #include "GtkVersioning.h" #include "WebEventFactory.h" +#include namespace WebKit { @@ -39,25 +40,64 @@ namespace WebKit { #define constructNativeEvent(event) gdk_event_copy(event) #endif -NativeWebKeyboardEvent::NativeWebKeyboardEvent(GdkEvent* event, const String& text, bool isAutoRepeat, Vector&& commands) - : WebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, text, isAutoRepeat, false, std::nullopt, std::nullopt, WTF::move(commands))) - , m_nativeEvent(constructNativeEvent(event)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebKeyboardEvent); + +Ref NativeWebKeyboardEvent::create(GdkEvent* event, const String& text, bool isAutoRepeat, Vector&& commands) +{ + return adoptRef(*new NativeWebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, text, isAutoRepeat, false, std::nullopt, std::nullopt, WTF::move(commands)), event)); +} + +Ref NativeWebKeyboardEvent::create(const String& text, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) { + return adoptRef(*new NativeWebKeyboardEvent(WebKeyboardEventInit { + { WebEventType::KeyDown, { }, MonotonicTime::now() }, + { + .text = text, + .key = "Unidentified"_s, + .code = "Unidentified"_s, + .keyIdentifier = "U+0000"_s, + .windowsVirtualKeyCode = 229, + .nativeVirtualKeyCode = GDK_KEY_VoidSymbol, + .handledByInputMethod = true, + .preeditUnderlines = WTF::move(preeditUnderlines), + .preeditSelectionRange = WTF::move(preeditSelectionRange), + .commands = { }, + .isAutoRepeat = false, + .isKeypad = false, + } + }, nullptr)); } -NativeWebKeyboardEvent::NativeWebKeyboardEvent(const String& text, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) - : WebKeyboardEvent(WebEvent(WebEventType::KeyDown, { }, MonotonicTime::now()), text, "Unidentified"_s, "Unidentified"_s, "U+0000"_s, 229, GDK_KEY_VoidSymbol, true, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange), { }, false, false) +Ref NativeWebKeyboardEvent::create(WebEventType type, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, Vector&& commands, bool isAutoRepeat, bool isKeypad, OptionSet modifiers) { + return adoptRef(*new NativeWebKeyboardEvent(WebKeyboardEventInit { + { type, modifiers, MonotonicTime::now() }, + { + .text = text, + .key = key, + .code = code, + .keyIdentifier = keyIdentifier, + .windowsVirtualKeyCode = windowsVirtualKeyCode, + .nativeVirtualKeyCode = nativeVirtualKeyCode, + .handledByInputMethod = false, + .preeditUnderlines = std::nullopt, + .preeditSelectionRange = std::nullopt, + .commands = WTF::move(commands), + .isAutoRepeat = isAutoRepeat, + .isKeypad = isKeypad, + } + }, nullptr)); } -NativeWebKeyboardEvent::NativeWebKeyboardEvent(WebEventType type, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, Vector&& commands, bool isAutoRepeat, bool isKeypad, OptionSet modifiers) - : WebKeyboardEvent(WebEvent(type, modifiers, MonotonicTime::now()), text, key, code, keyIdentifier, windowsVirtualKeyCode, nativeVirtualKeyCode, false, std::nullopt, std::nullopt, WTF::move(commands), isAutoRepeat, isKeypad) +Ref NativeWebKeyboardEvent::create(const NativeWebKeyboardEvent& event) { + return adoptRef(*new NativeWebKeyboardEvent(WebKeyboardEventInit { event.eventData(), event.keyboardData() }, + event.nativeEvent() ? const_cast(event.nativeEvent()) : nullptr)); } -NativeWebKeyboardEvent::NativeWebKeyboardEvent(const NativeWebKeyboardEvent& event) - : WebKeyboardEvent(event) - , m_nativeEvent(event.nativeEvent() ? constructNativeEvent(event.nativeEvent()) : nullptr) +NativeWebKeyboardEvent::NativeWebKeyboardEvent(WebKeyboardEventInit&& init, GdkEvent* event) + : WebKeyboardEvent(WTF::move(init.event), WTF::move(init.keyboard)) + , m_nativeEvent(event ? constructNativeEvent(event) : nullptr) { } diff --git a/Source/WebKit/Shared/gtk/NativeWebMouseEventGtk.cpp b/Source/WebKit/Shared/gtk/NativeWebMouseEventGtk.cpp index 85dae6250ad2..fc3ef4c55fc4 100644 --- a/Source/WebKit/Shared/gtk/NativeWebMouseEventGtk.cpp +++ b/Source/WebKit/Shared/gtk/NativeWebMouseEventGtk.cpp @@ -28,6 +28,7 @@ #include "GtkVersioning.h" #include "WebEventFactory.h" +#include namespace WebKit { @@ -37,31 +38,59 @@ namespace WebKit { #define constructNativeEvent(event) gdk_event_copy(event) #endif -NativeWebMouseEvent::NativeWebMouseEvent(GdkEvent* event, int eventClickCount, std::optional delta) - : WebMouseEvent(WebEventFactory::createWebMouseEvent(event, eventClickCount, delta)) - , m_nativeEvent(constructNativeEvent(event)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebMouseEvent); + +Ref NativeWebMouseEvent::create(GdkEvent* event, int eventClickCount, std::optional delta) +{ + return adoptRef(*new NativeWebMouseEvent(WebEventFactory::createWebMouseEvent(event, eventClickCount, delta), event)); +} + +Ref NativeWebMouseEvent::create(GdkEvent* event, const WebCore::DoublePoint& position, int eventClickCount, std::optional delta) { + return adoptRef(*new NativeWebMouseEvent(WebEventFactory::createWebMouseEvent(event, position, eventClickCount, delta), event)); } -NativeWebMouseEvent::NativeWebMouseEvent(GdkEvent* event, const WebCore::DoublePoint& position, int eventClickCount, std::optional delta) - : WebMouseEvent(WebEventFactory::createWebMouseEvent(event, position, eventClickCount, delta)) - , m_nativeEvent(constructNativeEvent(event)) +Ref NativeWebMouseEvent::create(const WebCore::DoublePoint& position) { + return adoptRef(*new NativeWebMouseEvent(WebEventFactory::createWebMouseEvent(position), nullptr)); } -NativeWebMouseEvent::NativeWebMouseEvent(const WebCore::DoublePoint& position) - : WebMouseEvent(WebEventFactory::createWebMouseEvent(position)) +Ref NativeWebMouseEvent::create(WebEventType type, WebMouseEventButton button, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, int clickCount, OptionSet modifiers, std::optional delta, WebCore::PointerID pointerId, const String& pointerType, WebCore::PlatformMouseEvent::IsTouch isTouchEvent) { + return adoptRef(*new NativeWebMouseEvent(WebMouseEventInit { + { type, modifiers, MonotonicTime::now() }, + { + .button = button, + .buttons = buttons, + .position = position, + .globalPosition = globalPosition, + .deltaX = delta.value_or(WebCore::FloatSize()).width(), + .deltaY = delta.value_or(WebCore::FloatSize()).height(), + .deltaZ = 0, + .clickCount = clickCount, + .force = 0, + .inputSource = WebEventInputSource::UserDriven, + .canInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, + .syntheticClickType = WebMouseEventSyntheticClickType::NoTap, + .isTouchEvent = isTouchEvent, + .pointerId = pointerId, + .pointerType = pointerType, + .gestureWasCancelled = GestureWasCancelled::No, + .unadjustedMovementDelta = { }, + .coalescedEvents = { }, + } + }, nullptr)); } -NativeWebMouseEvent::NativeWebMouseEvent(WebEventType type, WebMouseEventButton button, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, int clickCount, OptionSet modifiers, std::optional delta, WebCore::PointerID pointerId, const String& pointerType, WebCore::PlatformMouseEvent::IsTouch isTouchEvent) - : WebMouseEvent(WebEvent(type, modifiers, MonotonicTime::now()), button, buttons, position, globalPosition, delta.value_or(WebCore::FloatSize()).width(), delta.value_or(WebCore::FloatSize()).height(), 0, clickCount, 0, WebEventInputSource::UserDriven, WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, WebMouseEventSyntheticClickType::NoTap, isTouchEvent, pointerId, pointerType) +Ref NativeWebMouseEvent::create(const NativeWebMouseEvent& event) { + return adoptRef(*new NativeWebMouseEvent(WebMouseEventInit { event.eventData(), event.mouseData() }, + event.nativeEvent() ? const_cast(event.nativeEvent()) : nullptr)); } -NativeWebMouseEvent::NativeWebMouseEvent(const NativeWebMouseEvent& event) - : WebMouseEvent(event) - , m_nativeEvent(event.nativeEvent() ? constructNativeEvent(const_cast(event.nativeEvent())) : nullptr) +NativeWebMouseEvent::NativeWebMouseEvent(WebMouseEventInit&& init, GdkEvent* event) + : WebMouseEvent(WTF::move(init.event), WTF::move(init.mouse)) + , m_nativeEvent(event ? constructNativeEvent(event) : nullptr) { } diff --git a/Source/WebKit/Shared/gtk/NativeWebTouchEventGtk.cpp b/Source/WebKit/Shared/gtk/NativeWebTouchEventGtk.cpp index 5f55875092ab..bf111775eea5 100644 --- a/Source/WebKit/Shared/gtk/NativeWebTouchEventGtk.cpp +++ b/Source/WebKit/Shared/gtk/NativeWebTouchEventGtk.cpp @@ -30,6 +30,7 @@ #include "GtkVersioning.h" #include "WebEventFactory.h" +#include #include namespace WebKit { @@ -40,20 +41,30 @@ namespace WebKit { #define constructNativeEvent(event) gdk_event_copy(event) #endif -NativeWebTouchEvent::NativeWebTouchEvent(GdkEvent* event, Vector&& touchPoints) - : WebTouchEvent(WebEventFactory::createWebTouchEvent(event, WTF::move(touchPoints))) - , m_nativeEvent(constructNativeEvent(event)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebTouchEvent); + +Ref NativeWebTouchEvent::create(GdkEvent* event, Vector&& touchPoints) +{ + return adoptRef(*new NativeWebTouchEvent(WebEventFactory::createWebTouchEvent(event, WTF::move(touchPoints)), event)); +} + +Ref NativeWebTouchEvent::create(WebEventType type, OptionSet modifiers, Vector&& touchPoints) { + return adoptRef(*new NativeWebTouchEvent(WebTouchEventInit { + { type, modifiers, MonotonicTime::now() }, + { .touchPoints = WTF::move(touchPoints), .coalescedEvents = { }, .predictedEvents = { } } + }, nullptr)); } -NativeWebTouchEvent::NativeWebTouchEvent(WebEventType type, OptionSet modifiers, Vector&& touchPoints) - : WebTouchEvent({ type, modifiers, MonotonicTime::now() }, WTF::move(touchPoints), { }, { }) +Ref NativeWebTouchEvent::create(const NativeWebTouchEvent& event) { + return adoptRef(*new NativeWebTouchEvent(WebTouchEventInit { event.eventData(), event.touchData() }, + const_cast(event.nativeEvent()))); } -NativeWebTouchEvent::NativeWebTouchEvent(const NativeWebTouchEvent& event) - : WebTouchEvent(event) - , m_nativeEvent(constructNativeEvent(const_cast(event.nativeEvent()))) +NativeWebTouchEvent::NativeWebTouchEvent(WebTouchEventInit&& init, GdkEvent* event) + : WebTouchEvent(WTF::move(init.event), WTF::move(init.touch)) + , m_nativeEvent(event ? constructNativeEvent(event) : nullptr) { } diff --git a/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp b/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp index 867701000d6e..78bda6388e35 100644 --- a/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp +++ b/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp @@ -28,6 +28,7 @@ #include "GtkVersioning.h" #include "WebEventFactory.h" +#include #if USE(GTK4) #define constructNativeEvent(event) event @@ -37,15 +38,22 @@ namespace WebKit { -NativeWebWheelEvent::NativeWebWheelEvent(GdkEvent* event, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase, bool hasPreciseDeltas) - : WebWheelEvent(WebEventFactory::createWebWheelEvent(event, position, globalPosition, delta, wheelTicks, phase, momentumPhase, hasPreciseDeltas)) - , m_nativeEvent(event ? constructNativeEvent(event) : nullptr) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebWheelEvent); + +Ref NativeWebWheelEvent::create(GdkEvent* event, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase, bool hasPreciseDeltas) +{ + return adoptRef(*new NativeWebWheelEvent(WebEventFactory::createWebWheelEvent(event, position, globalPosition, delta, wheelTicks, phase, momentumPhase, hasPreciseDeltas), event)); +} + +Ref NativeWebWheelEvent::create(const NativeWebWheelEvent& event) { + return adoptRef(*new NativeWebWheelEvent(WebWheelEventInit { event.eventData(), event.wheelData() }, + event.nativeEvent() ? const_cast(event.nativeEvent()) : nullptr)); } -NativeWebWheelEvent::NativeWebWheelEvent(const NativeWebWheelEvent& event) - : WebWheelEvent(event) - , m_nativeEvent(event.nativeEvent() ? constructNativeEvent(event.nativeEvent()) : nullptr) +NativeWebWheelEvent::NativeWebWheelEvent(WebWheelEventInit&& init, GdkEvent* event) + : WebWheelEvent(WTF::move(init.event), WTF::move(init.wheel)) + , m_nativeEvent(event ? constructNativeEvent(event) : nullptr) { } diff --git a/Source/WebKit/Shared/gtk/WebEventFactory.cpp b/Source/WebKit/Shared/gtk/WebEventFactory.cpp index 7d581570cce5..baaf77327ce7 100644 --- a/Source/WebKit/Shared/gtk/WebEventFactory.cpp +++ b/Source/WebKit/Shared/gtk/WebEventFactory.cpp @@ -200,7 +200,7 @@ static inline short pressedMouseButtons(GdkModifierType state) return buttons; } -WebMouseEvent WebEventFactory::createWebMouseEvent(const GdkEvent* event, int currentClickCount, std::optional delta) +WebMouseEventInit WebEventFactory::createWebMouseEvent(const GdkEvent* event, int currentClickCount, std::optional delta) { double x, y; gdk_event_get_coords(event, &x, &y); @@ -208,7 +208,7 @@ WebMouseEvent WebEventFactory::createWebMouseEvent(const GdkEvent* event, int cu return createWebMouseEvent(event, DoublePoint(x, y), currentClickCount, delta); } -WebMouseEvent WebEventFactory::createWebMouseEvent(const GdkEvent* event, const DoublePoint& position, int currentClickCount, std::optional delta) +WebMouseEventInit WebEventFactory::createWebMouseEvent(const GdkEvent* event, const DoublePoint& position, int currentClickCount, std::optional delta) { #if USE(GTK4) // This can happen when a NativeWebMouseEvent representing a crossing event is copied. @@ -257,27 +257,60 @@ WebMouseEvent WebEventFactory::createWebMouseEvent(const GdkEvent* event, const ASSERT_NOT_REACHED(); } - return WebMouseEvent({ type, modifiersForEvent(event), monotonicTimeForEvent(event) }, - buttonForEvent(event), - pressedMouseButtons(state), - position, - globalPosition, - movementDelta.width(), - movementDelta.height(), - 0 /* deltaZ */, - currentClickCount, - 0 /* force */, - WebEventInputSource::UserDriven - ); + return { + { type, modifiersForEvent(event), monotonicTimeForEvent(event) }, + { + .button = buttonForEvent(event), + .buttons = static_cast(pressedMouseButtons(state)), + .position = position, + .globalPosition = globalPosition, + .deltaX = movementDelta.width(), + .deltaY = movementDelta.height(), + .deltaZ = 0, + .clickCount = currentClickCount, + .force = 0, + .inputSource = WebEventInputSource::UserDriven, + .canInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, + .syntheticClickType = WebMouseEventSyntheticClickType::NoTap, + .isTouchEvent = WebCore::PlatformMouseEvent::IsTouch::No, + .pointerId = WebCore::mousePointerID, + .pointerType = WebCore::mousePointerEventType(), + .gestureWasCancelled = GestureWasCancelled::No, + .unadjustedMovementDelta = { }, + .coalescedEvents = { }, + } + }; } -WebMouseEvent WebEventFactory::createWebMouseEvent(const DoublePoint& position) +WebMouseEventInit WebEventFactory::createWebMouseEvent(const DoublePoint& position) { // Mouse events without GdkEvent are crossing events, handled as a mouse move. - return WebMouseEvent({ WebEventType::MouseMove, { }, MonotonicTime::now() }, WebMouseEventButton::None, 0, position, position, 0, 0, 0, 0, 0, WebEventInputSource::UserDriven); + return { + { WebEventType::MouseMove, { }, MonotonicTime::now() }, + { + .button = WebMouseEventButton::None, + .buttons = 0, + .position = position, + .globalPosition = position, + .deltaX = 0, + .deltaY = 0, + .deltaZ = 0, + .clickCount = 0, + .force = 0, + .inputSource = WebEventInputSource::UserDriven, + .canInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, + .syntheticClickType = WebMouseEventSyntheticClickType::NoTap, + .isTouchEvent = WebCore::PlatformMouseEvent::IsTouch::No, + .pointerId = WebCore::mousePointerID, + .pointerType = WebCore::mousePointerEventType(), + .gestureWasCancelled = GestureWasCancelled::No, + .unadjustedMovementDelta = { }, + .coalescedEvents = { }, + } + }; } -WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(const GdkEvent* event, const String& text, bool isAutoRepeat, bool handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange, Vector&& commands) +WebKeyboardEventInit WebEventFactory::createWebKeyboardEvent(const GdkEvent* event, const String& text, bool isAutoRepeat, bool handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange, Vector&& commands) { guint keyval; gdk_event_get_keyval(event, &keyval); @@ -285,25 +318,27 @@ WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(const GdkEvent* event, gdk_event_get_keycode(event, &keycode); GdkEventType type = gdk_event_get_event_type(const_cast(event)); - return WebKeyboardEvent( + return { { type == GDK_KEY_RELEASE ? WebEventType::KeyUp : WebEventType::KeyDown, modifiersForEvent(event), monotonicTimeForEvent(event) }, - text.isNull() ? WebKeyboardEvent::singleCharacterStringForGdkKeyval(keyval) : text, - WebKeyboardEvent::keyValueStringForGdkKeyval(keyval), - WebKeyboardEvent::keyCodeStringForGdkKeycode(keycode), - WebKeyboardEvent::keyIdentifierForGdkKeyval(keyval), - WebKeyboardEvent::windowsKeyCodeForGdkKeyval(keyval), - static_cast(keyval), - handledByInputMethod, - WTF::move(preeditUnderlines), - WTF::move(preeditSelectionRange), - WTF::move(commands), - isAutoRepeat, - isGdkKeyCodeFromKeyPad(keyval) - ); + { + .text = text.isNull() ? WebKeyboardEvent::singleCharacterStringForGdkKeyval(keyval) : text, + .key = WebKeyboardEvent::keyValueStringForGdkKeyval(keyval), + .code = WebKeyboardEvent::keyCodeStringForGdkKeycode(keycode), + .keyIdentifier = WebKeyboardEvent::keyIdentifierForGdkKeyval(keyval), + .windowsVirtualKeyCode = WebKeyboardEvent::windowsKeyCodeForGdkKeyval(keyval), + .nativeVirtualKeyCode = static_cast(keyval), + .handledByInputMethod = handledByInputMethod, + .preeditUnderlines = WTF::move(preeditUnderlines), + .preeditSelectionRange = WTF::move(preeditSelectionRange), + .commands = WTF::move(commands), + .isAutoRepeat = isAutoRepeat, + .isKeypad = isGdkKeyCodeFromKeyPad(keyval), + } + }; } #if ENABLE(TOUCH_EVENTS) -WebTouchEvent WebEventFactory::createWebTouchEvent(const GdkEvent* event, Vector&& touchPoints) +WebTouchEventInit WebEventFactory::createWebTouchEvent(const GdkEvent* event, Vector&& touchPoints) { auto type = WebEventType::TouchMove; GdkEventType eventType = gdk_event_get_event_type(const_cast(event)); @@ -324,13 +359,25 @@ WebTouchEvent WebEventFactory::createWebTouchEvent(const GdkEvent* event, Vector ASSERT_NOT_REACHED(); } - return WebTouchEvent({ type, modifiersForEvent(event), monotonicTimeForEvent(event) }, WTF::move(touchPoints), { }, { }); + return { { type, modifiersForEvent(event), monotonicTimeForEvent(event) }, { .touchPoints = WTF::move(touchPoints), .coalescedEvents = { }, .predictedEvents = { } } }; } #endif -WebWheelEvent WebEventFactory::createWebWheelEvent(const GdkEvent* event, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase, bool hasPreciseDeltas) +WebWheelEventInit WebEventFactory::createWebWheelEvent(const GdkEvent* event, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase, bool hasPreciseDeltas) { - return WebWheelEvent({ WebEventType::Wheel, modifiersForEvent(event), monotonicTimeForEvent(event) }, position, globalPosition, delta, wheelTicks, WebWheelEvent::Granularity::ScrollByPixelWheelEvent, phase, momentumPhase, hasPreciseDeltas); + return { + { WebEventType::Wheel, modifiersForEvent(event), monotonicTimeForEvent(event) }, + { + .position = position, + .globalPosition = globalPosition, + .delta = delta, + .wheelTicks = wheelTicks, + .granularity = WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .phase = phase, + .momentumPhase = momentumPhase, + .hasPreciseScrollingDeltas = hasPreciseDeltas, + } + }; } } // namespace WebKit diff --git a/Source/WebKit/Shared/gtk/WebEventFactory.h b/Source/WebKit/Shared/gtk/WebEventFactory.h index ce0a85cc9360..55e29a44f145 100644 --- a/Source/WebKit/Shared/gtk/WebEventFactory.h +++ b/Source/WebKit/Shared/gtk/WebEventFactory.h @@ -44,14 +44,14 @@ namespace WebKit { class WebEventFactory { public: - static WebMouseEvent createWebMouseEvent(const GdkEvent*, int, std::optional); - static WebMouseEvent createWebMouseEvent(const GdkEvent*, const WebCore::DoublePoint&, int, std::optional); - static WebMouseEvent createWebMouseEvent(const WebCore::DoublePoint&); - static WebKeyboardEvent createWebKeyboardEvent(const GdkEvent*, const String&, bool isAutoRepeat, bool handledByInputMethod, std::optional>&&, std::optional&&, Vector&& commands); + static WebMouseEventInit createWebMouseEvent(const GdkEvent*, int, std::optional); + static WebMouseEventInit createWebMouseEvent(const GdkEvent*, const WebCore::DoublePoint&, int, std::optional); + static WebMouseEventInit createWebMouseEvent(const WebCore::DoublePoint&); + static WebKeyboardEventInit createWebKeyboardEvent(const GdkEvent*, const String&, bool isAutoRepeat, bool handledByInputMethod, std::optional>&&, std::optional&&, Vector&& commands); #if ENABLE(TOUCH_EVENTS) - static WebTouchEvent createWebTouchEvent(const GdkEvent*, Vector&&); + static WebTouchEventInit createWebTouchEvent(const GdkEvent*, Vector&&); #endif - static WebWheelEvent createWebWheelEvent(const GdkEvent*, const WebCore::IntPoint&, const WebCore::IntPoint&, const WebCore::FloatSize&, const WebCore::FloatSize&, WebWheelEvent::Phase, WebWheelEvent::Phase, bool hasPreciseDeltas); + static WebWheelEventInit createWebWheelEvent(const GdkEvent*, const WebCore::IntPoint&, const WebCore::IntPoint&, const WebCore::FloatSize&, const WebCore::FloatSize&, WebWheelEvent::Phase, WebWheelEvent::Phase, bool hasPreciseDeltas); }; } // namespace WebKit diff --git a/Source/WebKit/Shared/ios/NativeWebKeyboardEventIOS.mm b/Source/WebKit/Shared/ios/NativeWebKeyboardEventIOS.mm index a38ef8786e16..83dd71f40856 100644 --- a/Source/WebKit/Shared/ios/NativeWebKeyboardEventIOS.mm +++ b/Source/WebKit/Shared/ios/NativeWebKeyboardEventIOS.mm @@ -31,11 +31,19 @@ #import "UIKitSPI.h" #import "WebIOSEventFactory.h" #import +#import namespace WebKit { -NativeWebKeyboardEvent::NativeWebKeyboardEvent(::WebEvent *event, HandledByInputMethod handledByInputMethod) - : WebKeyboardEvent(WebIOSEventFactory::createWebKeyboardEvent(event, handledByInputMethod == HandledByInputMethod::Yes)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebKeyboardEvent); + +Ref NativeWebKeyboardEvent::create(::WebEvent *event, HandledByInputMethod handledByInputMethod) +{ + return adoptRef(*new NativeWebKeyboardEvent(WebIOSEventFactory::createWebKeyboardEvent(event, handledByInputMethod == HandledByInputMethod::Yes), event)); +} + +NativeWebKeyboardEvent::NativeWebKeyboardEvent(WebKeyboardEventInit&& init, ::WebEvent *event) + : WebKeyboardEvent(WTF::move(init.event), WTF::move(init.keyboard)) , m_nativeEvent(event) { } diff --git a/Source/WebKit/Shared/ios/NativeWebMouseEventIOS.mm b/Source/WebKit/Shared/ios/NativeWebMouseEventIOS.mm index 0cd6d082beba..7374b4595f08 100644 --- a/Source/WebKit/Shared/ios/NativeWebMouseEventIOS.mm +++ b/Source/WebKit/Shared/ios/NativeWebMouseEventIOS.mm @@ -29,22 +29,70 @@ #if PLATFORM(IOS_FAMILY) #import "WebIOSEventFactory.h" +#import namespace WebKit { -NativeWebMouseEvent::NativeWebMouseEvent(::WebEvent *event) - : WebMouseEvent(WebIOSEventFactory::createWebMouseEvent(event)) - , m_nativeEvent(event) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebMouseEvent); + +Ref NativeWebMouseEvent::create(::WebEvent *event) +{ + return adoptRef(*new NativeWebMouseEvent(WebIOSEventFactory::createWebMouseEvent(event), event)); +} + +Ref NativeWebMouseEvent::create(WebEventType type, WebMouseEventButton button, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, OptionSet modifiers, MonotonicTime timestamp, double force, GestureWasCancelled gestureWasCancelled, const String& pointerType) { + return adoptRef(*new NativeWebMouseEvent(WebMouseEventInit { + { type, modifiers, timestamp }, + { + .button = button, + .buttons = buttons, + .position = position, + .globalPosition = globalPosition, + .deltaX = deltaX, + .deltaY = deltaY, + .deltaZ = deltaZ, + .clickCount = clickCount, + .force = force, + .inputSource = WebEventInputSource::UserDriven, + .canInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, + .syntheticClickType = WebMouseEventSyntheticClickType::NoTap, + .pointerId = WebCore::mousePointerID, + .pointerType = pointerType, + .gestureWasCancelled = gestureWasCancelled, + .unadjustedMovementDelta = { deltaX, deltaY }, + } + }, nil)); } -NativeWebMouseEvent::NativeWebMouseEvent(WebEventType type, WebMouseEventButton button, unsigned short buttons, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ, int clickCount, OptionSet modifiers, MonotonicTime timestamp, double force, GestureWasCancelled gestureWasCancelled, const String& pointerType) - : WebMouseEvent({ type, modifiers, timestamp }, button, buttons, position, globalPosition, deltaX, deltaY, deltaZ, clickCount, force, WebEventInputSource::UserDriven, WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, WebMouseEventSyntheticClickType::NoTap, WebCore::mousePointerID, pointerType, gestureWasCancelled, { deltaX, deltaY }) +Ref NativeWebMouseEvent::create(const NativeWebMouseEvent& otherEvent, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ) { + return adoptRef(*new NativeWebMouseEvent(WebMouseEventInit { + { otherEvent.type(), otherEvent.modifiers(), otherEvent.timestamp() }, + { + .button = otherEvent.button(), + .buttons = otherEvent.buttons(), + .position = position, + .globalPosition = globalPosition, + .deltaX = deltaX, + .deltaY = deltaY, + .deltaZ = deltaZ, + .clickCount = otherEvent.clickCount(), + .force = otherEvent.force(), + .inputSource = otherEvent.inputSource(), + .canInitiateDrag = otherEvent.canInitiateDrag(), + .syntheticClickType = otherEvent.syntheticClickType(), + .pointerId = otherEvent.pointerId(), + .pointerType = otherEvent.pointerType(), + .gestureWasCancelled = otherEvent.gestureWasCancelled(), + .unadjustedMovementDelta = { deltaX, deltaY }, + } + }, nil)); } -NativeWebMouseEvent::NativeWebMouseEvent(const NativeWebMouseEvent& otherEvent, const WebCore::DoublePoint& position, const WebCore::DoublePoint& globalPosition, float deltaX, float deltaY, float deltaZ) - : WebMouseEvent({ otherEvent.type(), otherEvent.modifiers(), otherEvent.timestamp() }, otherEvent.button(), otherEvent.buttons(), position, globalPosition, deltaX, deltaY, deltaZ, otherEvent.clickCount(), otherEvent.force(), otherEvent.inputSource(), otherEvent.canInitiateDrag(), otherEvent.syntheticClickType(), otherEvent.pointerId(), otherEvent.pointerType(), otherEvent.gestureWasCancelled(), { deltaX, deltaY }) +NativeWebMouseEvent::NativeWebMouseEvent(WebMouseEventInit&& init, ::WebEvent *event) + : WebMouseEvent(WTF::move(init.event), WTF::move(init.mouse)) + , m_nativeEvent(event) { } diff --git a/Source/WebKit/Shared/ios/NativeWebTouchEventIOS.mm b/Source/WebKit/Shared/ios/NativeWebTouchEventIOS.mm index 1781ea3dfc7d..980684b29d9d 100644 --- a/Source/WebKit/Shared/ios/NativeWebTouchEventIOS.mm +++ b/Source/WebKit/Shared/ios/NativeWebTouchEventIOS.mm @@ -31,11 +31,14 @@ #import "WKTouchEventsGestureRecognizer.h" #import #import +#import namespace WebKit { #if ENABLE(TOUCH_EVENTS) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebTouchEvent); + static inline WebEventType webEventTypeForWKTouchEventType(WKTouchEventType type) { switch (type) { @@ -121,31 +124,39 @@ static CGFloat radiusForTouchPoint(const WKTouchPoint& touchPoint) }); } -Vector NativeWebTouchEvent::extractCoalescedWebTouchEvents(const WKTouchEvent& event, UIKeyModifierFlags flags) +Vector> NativeWebTouchEvent::extractCoalescedWebTouchEvents(const WKTouchEvent& event, UIKeyModifierFlags flags) { - return event.coalescedEvents.map([&](auto& event) -> WebTouchEvent { - return NativeWebTouchEvent { event, flags }; + return event.coalescedEvents.map([&](auto& event) -> Ref { + return NativeWebTouchEvent::create(event, flags); }); } -Vector NativeWebTouchEvent::extractPredictedWebTouchEvents(const WKTouchEvent& event, UIKeyModifierFlags flags) +Vector> NativeWebTouchEvent::extractPredictedWebTouchEvents(const WKTouchEvent& event, UIKeyModifierFlags flags) { - return event.predictedEvents.map([&](auto& event) -> WebTouchEvent { - return NativeWebTouchEvent { event, flags }; + return event.predictedEvents.map([&](auto& event) -> Ref { + return NativeWebTouchEvent::create(event, flags); }); } -NativeWebTouchEvent::NativeWebTouchEvent(const WKTouchEvent& event, UIKeyModifierFlags flags) - : WebTouchEvent( +Ref NativeWebTouchEvent::create(const WKTouchEvent& event, UIKeyModifierFlags flags) +{ + return adoptRef(*new NativeWebTouchEvent(WebTouchEventInit { { webEventTypeForWKTouchEventType(event.type), webEventModifierFlags(flags), MonotonicTime::fromRawSeconds(event.timestamp) }, - extractWebTouchPoints(event), - extractCoalescedWebTouchEvents(event, flags), - extractPredictedWebTouchEvents(event, flags), - positionForCGPoint(event.locationInRootViewCoordinates), - event.isPotentialTap, - event.inJavaScriptGesture, - event.scale, - event.rotation) + { + .touchPoints = extractWebTouchPoints(event), + .coalescedEvents = extractCoalescedWebTouchEvents(event, flags), + .predictedEvents = extractPredictedWebTouchEvents(event, flags), + .position = positionForCGPoint(event.locationInRootViewCoordinates), + .isPotentialTap = event.isPotentialTap, + .isGesture = event.inJavaScriptGesture, + .gestureScale = static_cast(event.scale), + .gestureRotation = static_cast(event.rotation), + } + })); +} + +NativeWebTouchEvent::NativeWebTouchEvent(WebTouchEventInit&& init) + : WebTouchEvent(WTF::move(init.event), WTF::move(init.touch)) { } diff --git a/Source/WebKit/Shared/ios/WebIOSEventFactory.h b/Source/WebKit/Shared/ios/WebIOSEventFactory.h index 3facde4edf74..d7dd31ca5cda 100644 --- a/Source/WebKit/Shared/ios/WebIOSEventFactory.h +++ b/Source/WebKit/Shared/ios/WebIOSEventFactory.h @@ -41,11 +41,11 @@ namespace WebKit { class WebIOSEventFactory { public: - static WebKit::WebKeyboardEvent createWebKeyboardEvent(::WebEvent *, bool handledByInputMethod); - static WebKit::WebMouseEvent createWebMouseEvent(::WebEvent *); + static WebKit::WebKeyboardEventInit createWebKeyboardEvent(::WebEvent *, bool handledByInputMethod); + static WebKit::WebMouseEventInit createWebMouseEvent(::WebEvent *); #if HAVE(UISCROLLVIEW_ASYNCHRONOUS_SCROLL_EVENT_HANDLING) - static WebKit::WebWheelEvent createWebWheelEvent(WKBEScrollViewScrollUpdate *, UIView *contentView, std::optional overridePhase = std::nullopt); + static WebKit::WebWheelEventInit createWebWheelEvent(WKBEScrollViewScrollUpdate *, UIView *contentView, std::optional overridePhase = std::nullopt); static WebCore::FloatSize translationInView(WKBEScrollViewScrollUpdate *, UIView *); #endif diff --git a/Source/WebKit/Shared/ios/WebIOSEventFactory.mm b/Source/WebKit/Shared/ios/WebIOSEventFactory.mm index e8ec26c6d615..7a7264377435 100644 --- a/Source/WebKit/Shared/ios/WebIOSEventFactory.mm +++ b/Source/WebKit/Shared/ios/WebIOSEventFactory.mm @@ -103,7 +103,7 @@ return modifiers; } -WebKeyboardEvent WebIOSEventFactory::createWebKeyboardEvent(::WebEvent *event, bool handledByInputMethod) +WebKeyboardEventInit WebIOSEventFactory::createWebKeyboardEvent(::WebEvent *event, bool handledByInputMethod) { WebEventType type = (event.type == WebEventKeyUp) ? WebEventType::KeyUp : WebEventType::KeyDown; String text; @@ -148,10 +148,26 @@ unmodifiedText = text; } - return WebKeyboardEvent { { type, modifiers, MonotonicTime::fromRawSeconds(timestamp) }, text, unmodifiedText, key, code, keyIdentifier, windowsVirtualKeyCode, nativeVirtualKeyCode, macCharCode, handledByInputMethod, autoRepeat, isKeypad, isSystemKey }; + return { + { type, modifiers, MonotonicTime::fromRawSeconds(timestamp) }, + { + .text = text, + .unmodifiedText = unmodifiedText, + .key = key, + .code = code, + .keyIdentifier = keyIdentifier, + .windowsVirtualKeyCode = windowsVirtualKeyCode, + .nativeVirtualKeyCode = nativeVirtualKeyCode, + .macCharCode = macCharCode, + .handledByInputMethod = handledByInputMethod, + .isAutoRepeat = autoRepeat, + .isKeypad = isKeypad, + .isSystemKey = isSystemKey, + } + }; } -WebMouseEvent WebIOSEventFactory::createWebMouseEvent(::WebEvent *event) +WebMouseEventInit WebIOSEventFactory::createWebMouseEvent(::WebEvent *event) { // This currently only supports synthetic mouse moved events with no button pressed. ASSERT_ARG(event, event.type == WebEventMouseMoved); @@ -166,7 +182,21 @@ int clickCount = 0; double timestamp = event.timestamp; - return WebMouseEvent({ type, OptionSet { }, MonotonicTime::fromRawSeconds(timestamp) }, button, buttons, position, position, deltaX, deltaY, deltaZ, clickCount, 0, WebEventInputSource::UserDriven); + return { + { type, OptionSet { }, MonotonicTime::fromRawSeconds(timestamp) }, + { + .button = button, + .buttons = buttons, + .position = position, + .globalPosition = position, + .deltaX = deltaX, + .deltaY = deltaY, + .deltaZ = deltaZ, + .clickCount = clickCount, + .force = 0, + .inputSource = WebEventInputSource::UserDriven, + } + }; } #if HAVE(UISCROLLVIEW_ASYNCHRONOUS_SCROLL_EVENT_HANDLING) @@ -205,7 +235,7 @@ #endif } -WebWheelEvent WebIOSEventFactory::createWebWheelEvent(WKBEScrollViewScrollUpdate *update, UIView *contentView, std::optional overridePhase) +WebWheelEventInit WebIOSEventFactory::createWebWheelEvent(WKBEScrollViewScrollUpdate *update, UIView *contentView, std::optional overridePhase) { WebCore::IntPoint scrollLocation = WebCore::roundedIntPoint([update locationInView:contentView]); auto delta = translationInView(update, contentView); @@ -214,20 +244,22 @@ auto timestamp = MonotonicTime::fromRawSeconds(update.timestamp); return { { WebEventType::Wheel, OptionSet { }, timestamp }, - scrollLocation, - scrollLocation, - delta, - wheelTicks, - WebWheelEvent::Granularity::ScrollByPixelWheelEvent, - false, - overridePhase.value_or(toWebPhase(update.phase)), - WebWheelEvent::Phase::None, - true, - 1, - delta, - timestamp, - { }, - WebWheelEvent::MomentumEndType::Unknown + { + .position = scrollLocation, + .globalPosition = scrollLocation, + .delta = delta, + .wheelTicks = wheelTicks, + .granularity = WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .directionInvertedFromDevice = false, + .phase = overridePhase.value_or(toWebPhase(update.phase)), + .momentumPhase = WebWheelEvent::Phase::None, + .hasPreciseScrollingDeltas = true, + .scrollCount = 1, + .unacceleratedScrollingDelta = delta, + .ioHIDEventTimestamp = timestamp, + .rawPlatformDelta = { }, + .momentumEndType = WebWheelEvent::MomentumEndType::Unknown, + } }; } #endif diff --git a/Source/WebKit/Shared/libwpe/NativeWebKeyboardEventLibWPE.cpp b/Source/WebKit/Shared/libwpe/NativeWebKeyboardEventLibWPE.cpp index 306d78f0b3b7..e2ed0e480901 100644 --- a/Source/WebKit/Shared/libwpe/NativeWebKeyboardEventLibWPE.cpp +++ b/Source/WebKit/Shared/libwpe/NativeWebKeyboardEventLibWPE.cpp @@ -28,11 +28,19 @@ #if USE(LIBWPE) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebKeyboardEvent::NativeWebKeyboardEvent(struct wpe_input_keyboard_event* event, const String& text, bool isAutoRepeat, HandledByInputMethod handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) - : WebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, text, isAutoRepeat, handledByInputMethod == HandledByInputMethod::Yes, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange))) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebKeyboardEvent); + +Ref NativeWebKeyboardEvent::create(struct wpe_input_keyboard_event* event, const String& text, bool isAutoRepeat, HandledByInputMethod handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) +{ + return adoptRef(*new NativeWebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, text, isAutoRepeat, handledByInputMethod == HandledByInputMethod::Yes, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange)))); +} + +NativeWebKeyboardEvent::NativeWebKeyboardEvent(WebKeyboardEventInit&& init) + : WebKeyboardEvent(WTF::move(init.event), WTF::move(init.keyboard)) { } diff --git a/Source/WebKit/Shared/libwpe/NativeWebMouseEventLibWPE.cpp b/Source/WebKit/Shared/libwpe/NativeWebMouseEventLibWPE.cpp index 05ff09009da7..5d8c767c5e87 100644 --- a/Source/WebKit/Shared/libwpe/NativeWebMouseEventLibWPE.cpp +++ b/Source/WebKit/Shared/libwpe/NativeWebMouseEventLibWPE.cpp @@ -28,11 +28,19 @@ #if USE(LIBWPE) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebMouseEvent::NativeWebMouseEvent(struct wpe_input_pointer_event* event, float deviceScaleFactor, WebMouseEventSyntheticClickType syntheticClickType) - : WebMouseEvent(WebEventFactory::createWebMouseEvent(event, deviceScaleFactor, syntheticClickType)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebMouseEvent); + +Ref NativeWebMouseEvent::create(struct wpe_input_pointer_event* event, float deviceScaleFactor, WebMouseEventSyntheticClickType syntheticClickType) +{ + return adoptRef(*new NativeWebMouseEvent(WebEventFactory::createWebMouseEvent(event, deviceScaleFactor, syntheticClickType))); +} + +NativeWebMouseEvent::NativeWebMouseEvent(WebMouseEventInit&& init) + : WebMouseEvent(WTF::move(init.event), WTF::move(init.mouse)) { } diff --git a/Source/WebKit/Shared/libwpe/NativeWebTouchEventLibWPE.cpp b/Source/WebKit/Shared/libwpe/NativeWebTouchEventLibWPE.cpp index 13bb0ac83774..59de09f974ac 100644 --- a/Source/WebKit/Shared/libwpe/NativeWebTouchEventLibWPE.cpp +++ b/Source/WebKit/Shared/libwpe/NativeWebTouchEventLibWPE.cpp @@ -29,11 +29,19 @@ #if ENABLE(TOUCH_EVENTS) && USE(LIBWPE) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebTouchEvent::NativeWebTouchEvent(struct wpe_input_touch_event* event, float deviceScaleFactor) - : WebTouchEvent(WebEventFactory::createWebTouchEvent(event, deviceScaleFactor)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebTouchEvent); + +Ref NativeWebTouchEvent::create(struct wpe_input_touch_event* event, float deviceScaleFactor) +{ + return adoptRef(*new NativeWebTouchEvent(WebEventFactory::createWebTouchEvent(event, deviceScaleFactor), event)); +} + +NativeWebTouchEvent::NativeWebTouchEvent(WebTouchEventInit&& init, struct wpe_input_touch_event* event) + : WebTouchEvent(WTF::move(init.event), WTF::move(init.touch)) , m_fallbackTouchPoint { wpe_input_touch_event_type_null, 0, 0, 0, 0 } { for (auto& point : unsafeMakeSpan(event->touchpoints, event->touchpoints_length)) { diff --git a/Source/WebKit/Shared/libwpe/NativeWebWheelEventLibWPE.cpp b/Source/WebKit/Shared/libwpe/NativeWebWheelEventLibWPE.cpp index b05a9089a6ea..68be5b320c01 100644 --- a/Source/WebKit/Shared/libwpe/NativeWebWheelEventLibWPE.cpp +++ b/Source/WebKit/Shared/libwpe/NativeWebWheelEventLibWPE.cpp @@ -28,12 +28,20 @@ #if USE(LIBWPE) #include "WebEventFactory.h" +#include #include namespace WebKit { -NativeWebWheelEvent::NativeWebWheelEvent(struct wpe_input_axis_event* event, float deviceScaleFactor, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase) - : WebWheelEvent(WebEventFactory::createWebWheelEvent(event, deviceScaleFactor, phase, momentumPhase)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebWheelEvent); + +Ref NativeWebWheelEvent::create(struct wpe_input_axis_event* event, float deviceScaleFactor, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase) +{ + return adoptRef(*new NativeWebWheelEvent(WebEventFactory::createWebWheelEvent(event, deviceScaleFactor, phase, momentumPhase))); +} + +NativeWebWheelEvent::NativeWebWheelEvent(WebWheelEventInit&& init) + : WebWheelEvent(WTF::move(init.event), WTF::move(init.wheel)) { } diff --git a/Source/WebKit/Shared/libwpe/WebEventFactory.cpp b/Source/WebKit/Shared/libwpe/WebEventFactory.cpp index d7b30b4ffb9b..440a7f19b809 100644 --- a/Source/WebKit/Shared/libwpe/WebEventFactory.cpp +++ b/Source/WebKit/Shared/libwpe/WebEventFactory.cpp @@ -101,21 +101,24 @@ static OptionSet modifiersForKeyboardEvent(struct wpe_input_ke return modifiers; } -WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(struct wpe_input_keyboard_event* event, const String& text, bool isAutoRepeat, bool handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) +WebKeyboardEventInit WebEventFactory::createWebKeyboardEvent(struct wpe_input_keyboard_event* event, const String& text, bool isAutoRepeat, bool handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) { - return WebKeyboardEvent({ event->pressed ? WebEventType::KeyDown : WebEventType::KeyUp, modifiersForKeyboardEvent(event), monotonicTimeForEventTimeInMilliseconds(event->time) }, - text.isNull() ? WebCore::PlatformKeyboardEvent::singleCharacterString(event->key_code) : text, - WebCore::PlatformKeyboardEvent::keyValueForWPEKeyCode(event->key_code), - WebCore::PlatformKeyboardEvent::keyCodeForHardwareKeyCode(event->hardware_key_code), - WebCore::PlatformKeyboardEvent::keyIdentifierForWPEKeyCode(event->key_code), - WebCore::PlatformKeyboardEvent::windowsKeyCodeForWPEKeyCode(event->key_code), - event->key_code, - handledByInputMethod, - WTF::move(preeditUnderlines), - WTF::move(preeditSelectionRange), - isAutoRepeat, - isWPEKeyCodeFromKeyPad(event->key_code) - ); + return { + { event->pressed ? WebEventType::KeyDown : WebEventType::KeyUp, modifiersForKeyboardEvent(event), monotonicTimeForEventTimeInMilliseconds(event->time) }, + { + .text = text.isNull() ? WebCore::PlatformKeyboardEvent::singleCharacterString(event->key_code) : text, + .key = WebCore::PlatformKeyboardEvent::keyValueForWPEKeyCode(event->key_code), + .code = WebCore::PlatformKeyboardEvent::keyCodeForHardwareKeyCode(event->hardware_key_code), + .keyIdentifier = WebCore::PlatformKeyboardEvent::keyIdentifierForWPEKeyCode(event->key_code), + .windowsVirtualKeyCode = WebCore::PlatformKeyboardEvent::windowsKeyCodeForWPEKeyCode(event->key_code), + .nativeVirtualKeyCode = static_cast(event->key_code), + .handledByInputMethod = handledByInputMethod, + .preeditUnderlines = WTF::move(preeditUnderlines), + .preeditSelectionRange = WTF::move(preeditSelectionRange), + .isAutoRepeat = isAutoRepeat, + .isKeypad = isWPEKeyCodeFromKeyPad(event->key_code), + } + }; } static inline short pressedMouseButtons(uint32_t modifiers) @@ -170,7 +173,7 @@ static inline unsigned clickCount(struct wpe_input_pointer_event* event) return gLastClickCount; } -WebMouseEvent WebEventFactory::createWebMouseEvent(struct wpe_input_pointer_event* event, float deviceScaleFactor, WebMouseEventSyntheticClickType syntheticClickType) +WebMouseEventInit WebEventFactory::createWebMouseEvent(struct wpe_input_pointer_event* event, float deviceScaleFactor, WebMouseEventSyntheticClickType syntheticClickType) { auto type = WebEventType::MouseMove; switch (event->type) { @@ -204,11 +207,31 @@ WebMouseEvent WebEventFactory::createWebMouseEvent(struct wpe_input_pointer_even // FIXME: Proper button support. deltaX/Y/Z. WebCore::IntPoint position(event->x, event->y); position.scale(1 / deviceScaleFactor); - return WebMouseEvent({ type, modifiersForEventModifiers(event->modifiers), monotonicTimeForEventTimeInMilliseconds(event->time) }, button, pressedMouseButtons(event->modifiers), position, position, - 0, 0, 0, clickCount(event), 0, WebEventInputSource::UserDriven, WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, syntheticClickType); + return { + { type, modifiersForEventModifiers(event->modifiers), monotonicTimeForEventTimeInMilliseconds(event->time) }, + { + .button = button, + .buttons = static_cast(pressedMouseButtons(event->modifiers)), + .position = position, + .globalPosition = position, + .deltaX = 0, + .deltaY = 0, + .deltaZ = 0, + .clickCount = static_cast(clickCount(event)), + .force = 0, + .inputSource = WebEventInputSource::UserDriven, + .canInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, + .syntheticClickType = syntheticClickType, + .pointerId = WebCore::mousePointerID, + .pointerType = WebCore::mousePointerEventType(), + .gestureWasCancelled = GestureWasCancelled::No, + .unadjustedMovementDelta = { }, + .coalescedEvents = { }, + } + }; } -WebWheelEvent WebEventFactory::createWebWheelEvent(struct wpe_input_axis_event* event, float deviceScaleFactor, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase) +WebWheelEventInit WebEventFactory::createWebWheelEvent(struct wpe_input_axis_event* event, float deviceScaleFactor, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase) { WebCore::IntPoint position(event->x, event->y); position.scale(1 / deviceScaleFactor); @@ -235,9 +258,19 @@ WebWheelEvent WebEventFactory::createWebWheelEvent(struct wpe_input_axis_event* ASSERT_NOT_REACHED(); } - return WebWheelEvent({ WebEventType::Wheel, OptionSet { }, monotonicTimeForEventTimeInMilliseconds(event->time) }, position, position, - delta, wheelTicks, WebWheelEvent::Granularity::ScrollByPixelWheelEvent, phase, momentumPhase, - hasPreciseScrollingDeltas); + return { + { WebEventType::Wheel, OptionSet { }, monotonicTimeForEventTimeInMilliseconds(event->time) }, + { + .position = position, + .globalPosition = position, + .delta = delta, + .wheelTicks = wheelTicks, + .granularity = WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .phase = phase, + .momentumPhase = momentumPhase, + .hasPreciseScrollingDeltas = hasPreciseScrollingDeltas, + } + }; } #endif @@ -268,9 +301,19 @@ WebWheelEvent WebEventFactory::createWebWheelEvent(struct wpe_input_axis_event* ASSERT_NOT_REACHED(); }; - return WebWheelEvent({ WebEventType::Wheel, OptionSet { }, monotonicTimeForEventTimeInMilliseconds(event->time) }, position, position, - delta, wheelTicks, WebWheelEvent::Granularity::ScrollByPixelWheelEvent, phase, momentumPhase, - hasPreciseScrollingDeltas); + return { + { WebEventType::Wheel, OptionSet { }, monotonicTimeForEventTimeInMilliseconds(event->time) }, + { + .position = position, + .globalPosition = position, + .delta = delta, + .wheelTicks = wheelTicks, + .granularity = WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .phase = phase, + .momentumPhase = momentumPhase, + .hasPreciseScrollingDeltas = hasPreciseScrollingDeltas, + } + }; } #if ENABLE(TOUCH_EVENTS) @@ -294,7 +337,7 @@ static WebKit::WebPlatformTouchPoint::State stateForTouchPoint(int mainEventId, return WebKit::WebPlatformTouchPoint::State::Stationary; } -WebTouchEvent WebEventFactory::createWebTouchEvent(struct wpe_input_touch_event* event, float deviceScaleFactor) +WebTouchEventInit WebEventFactory::createWebTouchEvent(struct wpe_input_touch_event* event, float deviceScaleFactor) { WebEventType type; switch (event->type) { @@ -326,7 +369,7 @@ WebTouchEvent WebEventFactory::createWebTouchEvent(struct wpe_input_touch_event* pointCoordinates, pointCoordinates)); } - return WebTouchEvent({ type, OptionSet { }, monotonicTimeForEventTimeInMilliseconds(event->time) }, WTF::move(touchPoints), { }, { }); + return { { type, OptionSet { }, monotonicTimeForEventTimeInMilliseconds(event->time) }, { .touchPoints = WTF::move(touchPoints), .coalescedEvents = { }, .predictedEvents = { } } }; } #endif // ENABLE(TOUCH_EVENTS) diff --git a/Source/WebKit/Shared/libwpe/WebEventFactory.h b/Source/WebKit/Shared/libwpe/WebEventFactory.h index 284ab0fcbe6c..68af75416f5e 100644 --- a/Source/WebKit/Shared/libwpe/WebEventFactory.h +++ b/Source/WebKit/Shared/libwpe/WebEventFactory.h @@ -51,21 +51,21 @@ namespace WebKit { class WebEventFactory { public: #if USE(LIBWPE) - static WebKeyboardEvent createWebKeyboardEvent(struct wpe_input_keyboard_event*, const String&, bool isAutoRepeat, bool handledByInputMethod, std::optional>&&, std::optional&&); - static WebMouseEvent createWebMouseEvent(struct wpe_input_pointer_event*, float deviceScaleFactor, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap); - static WebWheelEvent createWebWheelEvent(struct wpe_input_axis_event*, float deviceScaleFactor, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase); + static WebKeyboardEventInit createWebKeyboardEvent(struct wpe_input_keyboard_event*, const String&, bool isAutoRepeat, bool handledByInputMethod, std::optional>&&, std::optional&&); + static WebMouseEventInit createWebMouseEvent(struct wpe_input_pointer_event*, float deviceScaleFactor, WebMouseEventSyntheticClickType = WebMouseEventSyntheticClickType::NoTap); + static WebWheelEventInit createWebWheelEvent(struct wpe_input_axis_event*, float deviceScaleFactor, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase); #if ENABLE(TOUCH_EVENTS) - static WebTouchEvent createWebTouchEvent(struct wpe_input_touch_event*, float deviceScaleFactor); + static WebTouchEventInit createWebTouchEvent(struct wpe_input_touch_event*, float deviceScaleFactor); #endif #endif #if PLATFORM(WPE) && ENABLE(WPE_PLATFORM) - static WebMouseEvent createWebMouseEvent(WPEEvent*); - static WebWheelEvent createWebWheelEvent(WPEEvent*); - static WebWheelEvent createWebWheelEvent(WPEEvent*, WebWheelEvent::Phase); - static WebKeyboardEvent createWebKeyboardEvent(WPEEvent*, const String&, bool isAutoRepeat); + static WebMouseEventInit createWebMouseEvent(WPEEvent*); + static WebWheelEventInit createWebWheelEvent(WPEEvent*); + static WebWheelEventInit createWebWheelEvent(WPEEvent*, WebWheelEvent::Phase); + static WebKeyboardEventInit createWebKeyboardEvent(WPEEvent*, const String&, bool isAutoRepeat); #if ENABLE(TOUCH_EVENTS) - static WebTouchEvent createWebTouchEvent(WPEEvent*, Vector&&); + static WebTouchEventInit createWebTouchEvent(WPEEvent*, Vector&&); #endif #endif }; diff --git a/Source/WebKit/Shared/mac/NativeWebGestureEventMac.mm b/Source/WebKit/Shared/mac/NativeWebGestureEventMac.mm index 4a21997a4033..a2db4aa03855 100644 --- a/Source/WebKit/Shared/mac/NativeWebGestureEventMac.mm +++ b/Source/WebKit/Shared/mac/NativeWebGestureEventMac.mm @@ -32,9 +32,12 @@ #import "WebGestureEvent.h" #import #import +#import namespace WebKit { +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebGestureEvent); + static inline std::optional webEventTypeForPhase(WebEventPhase phase) { switch (phase) { @@ -73,31 +76,33 @@ }; } -std::optional NativeWebGestureEvent::create(NSEvent *event, NSView *view) +RefPtr NativeWebGestureEvent::create(NSEvent *event, NSView *view) { return create(initForEvent(event), view, event); } -std::optional NativeWebGestureEvent::create(const Init& init, NSView *view) +RefPtr NativeWebGestureEvent::create(const Init& init, NSView *view) { return create(init, view, nil); } -std::optional NativeWebGestureEvent::create(const Init& init, NSView *view, NSEvent *event) +RefPtr NativeWebGestureEvent::create(const Init& init, NSView *view, NSEvent *event) { - return webEventTypeForPhase(init.phase). - and_then([&init, view = RetainPtr { view }, event = RetainPtr { event }](auto type) { - return std::optional { NativeWebGestureEvent { type, init, view, event } }; - }); + auto type = webEventTypeForPhase(init.phase); + if (!type) + return nullptr; + return adoptRef(*new NativeWebGestureEvent { *type, init, view, event }); } NativeWebGestureEvent::NativeWebGestureEvent(WebEventType type, const Init& init, NSView *view, NSEvent *event) : WebGestureEvent { - { type, { }, init.timestamp }, - positionInView(init.locationInWindow, view), - init.gestureScale, - init.gestureRotation, - init.phase } + WebEventData { type, { }, init.timestamp }, + WebGestureEventData { + .position = positionInView(init.locationInWindow, view), + .gestureScale = init.gestureScale, + .gestureRotation = init.gestureRotation, + .phase = init.phase, + } } , m_allowsNativeZoom(init.allowsNativeZoom) , m_kind(init.kind) , m_nativeEvent(event) diff --git a/Source/WebKit/Shared/mac/NativeWebKeyboardEventMac.mm b/Source/WebKit/Shared/mac/NativeWebKeyboardEventMac.mm index b70a762d5357..e0021ad41758 100644 --- a/Source/WebKit/Shared/mac/NativeWebKeyboardEventMac.mm +++ b/Source/WebKit/Shared/mac/NativeWebKeyboardEventMac.mm @@ -30,13 +30,21 @@ #import "WebEventFactory.h" #import +#import namespace WebKit { using namespace WebCore; -NativeWebKeyboardEvent::NativeWebKeyboardEvent(NSEvent *event, bool handledByInputMethod, bool replacesSoftSpace, const Vector& commands) - : WebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, handledByInputMethod, replacesSoftSpace, commands)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebKeyboardEvent); + +Ref NativeWebKeyboardEvent::create(NSEvent *event, bool handledByInputMethod, bool replacesSoftSpace, const Vector& commands) +{ + return adoptRef(*new NativeWebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, handledByInputMethod, replacesSoftSpace, commands), event)); +} + +NativeWebKeyboardEvent::NativeWebKeyboardEvent(WebKeyboardEventInit&& init, NSEvent *event) + : WebKeyboardEvent(WTF::move(init.event), WTF::move(init.keyboard)) , m_nativeEvent(event) { } diff --git a/Source/WebKit/Shared/mac/NativeWebMouseEventMac.mm b/Source/WebKit/Shared/mac/NativeWebMouseEventMac.mm index ef8494a004a6..0ae3fcb96ce5 100644 --- a/Source/WebKit/Shared/mac/NativeWebMouseEventMac.mm +++ b/Source/WebKit/Shared/mac/NativeWebMouseEventMac.mm @@ -29,11 +29,19 @@ #if USE(APPKIT) #import "WebEventFactory.h" +#import namespace WebKit { -NativeWebMouseEvent::NativeWebMouseEvent(NSEvent *event, NSEvent *lastPressureEvent, NSView *view, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag) - : WebMouseEvent(WebEventFactory::createWebMouseEvent(event, lastPressureEvent, view, inputSource, canInitiateDrag)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebMouseEvent); + +Ref NativeWebMouseEvent::create(NSEvent *event, NSEvent *lastPressureEvent, NSView *view, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag) +{ + return adoptRef(*new NativeWebMouseEvent(WebEventFactory::createWebMouseEvent(event, lastPressureEvent, view, inputSource, canInitiateDrag), event)); +} + +NativeWebMouseEvent::NativeWebMouseEvent(WebMouseEventInit&& init, NSEvent *event) + : WebMouseEvent(WTF::move(init.event), WTF::move(init.mouse)) , m_nativeEvent(event) { } diff --git a/Source/WebKit/Shared/mac/NativeWebWheelEventMac.mm b/Source/WebKit/Shared/mac/NativeWebWheelEventMac.mm index 70bdeb22d5b0..6473d4434488 100644 --- a/Source/WebKit/Shared/mac/NativeWebWheelEventMac.mm +++ b/Source/WebKit/Shared/mac/NativeWebWheelEventMac.mm @@ -29,18 +29,25 @@ #if USE(APPKIT) #import "WebEventFactory.h" +#import namespace WebKit { -NativeWebWheelEvent::NativeWebWheelEvent(NSEvent *event, NSView *view) - : WebWheelEvent(WebEventFactory::createWebWheelEvent(event, view)) - , m_nativeEvent(event) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebWheelEvent); + +Ref NativeWebWheelEvent::create(NSEvent *event, NSView *view) +{ + return adoptRef(*new NativeWebWheelEvent(WebEventFactory::createWebWheelEvent(event, view), event)); +} + +Ref NativeWebWheelEvent::create(const WebWheelEvent& wheelEvent) { + return adoptRef(*new NativeWebWheelEvent(WebWheelEventInit { wheelEvent.eventData(), wheelEvent.wheelData() }, nil)); } -NativeWebWheelEvent::NativeWebWheelEvent(const WebWheelEvent& wheelEvent) - : WebWheelEvent(wheelEvent) - , m_nativeEvent(nil) +NativeWebWheelEvent::NativeWebWheelEvent(WebWheelEventInit&& init, NSEvent *event) + : WebWheelEvent(WTF::move(init.event), WTF::move(init.wheel)) + , m_nativeEvent(event) { } diff --git a/Source/WebKit/Shared/mac/WebEventFactory.h b/Source/WebKit/Shared/mac/WebEventFactory.h index f0a0086c4085..34ef3cdb89e0 100644 --- a/Source/WebKit/Shared/mac/WebEventFactory.h +++ b/Source/WebKit/Shared/mac/WebEventFactory.h @@ -49,9 +49,9 @@ enum class WebEventPhase : uint8_t; class WebEventFactory { public: #if USE(APPKIT) - static WebMouseEvent createWebMouseEvent(NSEvent *, NSEvent *lastPressureEvent, NSView *windowView, WebEventInputSource, WebCore::PlatformMouseEvent::CanInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes); - static WebWheelEvent createWebWheelEvent(NSEvent *, NSView *windowView); - static WebKeyboardEvent createWebKeyboardEvent(NSEvent *, bool handledByInputMethod, bool replacesSoftSpace, const Vector&); + static WebMouseEventInit createWebMouseEvent(NSEvent *, NSEvent *lastPressureEvent, NSView *windowView, WebEventInputSource, WebCore::PlatformMouseEvent::CanInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes); + static WebWheelEventInit createWebWheelEvent(NSEvent *, NSView *windowView); + static WebKeyboardEventInit createWebKeyboardEvent(NSEvent *, bool handledByInputMethod, bool replacesSoftSpace, const Vector&); static bool NODELETE shouldBeHandledAsContextClick(const WebCore::PlatformMouseEvent&); #if defined(__OBJC__) diff --git a/Source/WebKit/Shared/mac/WebEventFactory.mm b/Source/WebKit/Shared/mac/WebEventFactory.mm index 313de6c3957d..9e5d6b78484d 100644 --- a/Source/WebKit/Shared/mac/WebEventFactory.mm +++ b/Source/WebKit/Shared/mac/WebEventFactory.mm @@ -120,7 +120,7 @@ static int typeForEvent(NSEvent *event) return (static_cast(event.menuTypeForEvent()) == NSMenuTypeContextMenu); } -WebMouseEvent WebEventFactory::createWebMouseEvent(NSEvent *event, NSEvent *lastPressureEvent, NSView *windowView, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag) +WebMouseEventInit WebEventFactory::createWebMouseEvent(NSEvent *event, NSEvent *lastPressureEvent, NSView *windowView, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag) { NSPoint position = WebCore::pointForEvent(event, windowView); NSPoint globalPosition = WebCore::globalPointForEvent(event); @@ -154,10 +154,30 @@ static int typeForEvent(NSEvent *event) auto unadjustedMovementDelta = WebCore::unadjustedMovementForEvent(event); - return WebMouseEvent({ type, modifiers, timestamp, WTF::UUID::createVersion4() }, button, buttons, WebCore::DoublePoint(position), WebCore::DoublePoint(globalPosition), deltaX, deltaY, deltaZ, clickCount, force, inputSource, canInitiateDrag, WebMouseEventSyntheticClickType::NoTap, eventNumber, menuTypeForEvent, GestureWasCancelled::No, unadjustedMovementDelta); + return { + { type, modifiers, timestamp }, + { + .button = button, + .buttons = buttons, + .position = WebCore::DoublePoint(position), + .globalPosition = WebCore::DoublePoint(globalPosition), + .deltaX = deltaX, + .deltaY = deltaY, + .deltaZ = deltaZ, + .clickCount = clickCount, + .force = force, + .inputSource = inputSource, + .canInitiateDrag = canInitiateDrag, + .syntheticClickType = WebMouseEventSyntheticClickType::NoTap, + .eventNumber = eventNumber, + .menuTypeForEvent = menuTypeForEvent, + .gestureWasCancelled = GestureWasCancelled::No, + .unadjustedMovementDelta = unadjustedMovementDelta, + } + }; } -WebWheelEvent WebEventFactory::createWebWheelEvent(NSEvent *event, NSView *windowView) +WebWheelEventInit WebEventFactory::createWebWheelEvent(NSEvent *event, NSView *windowView) { NSPoint position = WebCore::pointForEvent(event, windowView); NSPoint globalPosition = WebCore::globalPointForEvent(event); @@ -240,12 +260,28 @@ static int typeForEvent(NSEvent *event) rawPlatformDelta = std::nullopt; } - return WebWheelEvent({ WebEventType::Wheel, modifiers, timestamp, WTF::UUID::createVersion4() }, WebCore::IntPoint(position), WebCore::IntPoint(globalPosition), WebCore::FloatSize(deltaX, deltaY), WebCore::FloatSize(wheelTicksX, wheelTicksY), - granularity, directionInvertedFromDevice, phase, momentumPhase, hasPreciseScrollingDeltas, - scrollCount, unacceleratedScrollingDelta, ioHIDEventTimestamp, rawPlatformDelta, momentumEndType); + return { + { WebEventType::Wheel, modifiers, timestamp }, + { + .position = WebCore::IntPoint(position), + .globalPosition = WebCore::IntPoint(globalPosition), + .delta = WebCore::FloatSize(deltaX, deltaY), + .wheelTicks = WebCore::FloatSize(wheelTicksX, wheelTicksY), + .granularity = granularity, + .directionInvertedFromDevice = directionInvertedFromDevice, + .phase = phase, + .momentumPhase = momentumPhase, + .hasPreciseScrollingDeltas = hasPreciseScrollingDeltas, + .scrollCount = scrollCount, + .unacceleratedScrollingDelta = unacceleratedScrollingDelta, + .ioHIDEventTimestamp = ioHIDEventTimestamp, + .rawPlatformDelta = rawPlatformDelta, + .momentumEndType = momentumEndType, + } + }; } -WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(NSEvent *event, bool handledByInputMethod, bool replacesSoftSpace, const Vector& commands) +WebKeyboardEventInit WebEventFactory::createWebKeyboardEvent(NSEvent *event, bool handledByInputMethod, bool replacesSoftSpace, const Vector& commands) { WebEventType type = WebCore::isKeyUpEvent(event) ? WebEventType::KeyUp : WebEventType::KeyDown; String text = WebCore::textFromEvent(event, replacesSoftSpace); @@ -280,7 +316,24 @@ static int typeForEvent(NSEvent *event) unmodifiedText = text; } - return WebKeyboardEvent({ type, modifiers, timestamp, WTF::UUID::createVersion4() }, text, unmodifiedText, key, code, keyIdentifier, windowsVirtualKeyCode, nativeVirtualKeyCode, macCharCode, handledByInputMethod, commands, autoRepeat, isKeypad, isSystemKey); + return { + { type, modifiers, timestamp }, + { + .text = text, + .unmodifiedText = unmodifiedText, + .key = key, + .code = code, + .keyIdentifier = keyIdentifier, + .windowsVirtualKeyCode = windowsVirtualKeyCode, + .nativeVirtualKeyCode = nativeVirtualKeyCode, + .macCharCode = macCharCode, + .handledByInputMethod = handledByInputMethod, + .commands = commands, + .isAutoRepeat = autoRepeat, + .isKeypad = isKeypad, + .isSystemKey = isSystemKey, + } + }; } NSEventModifierFlags WebEventFactory::toNSEventModifierFlags(OptionSet modifiers) diff --git a/Source/WebKit/Shared/mac/WebGestureEvent.cpp b/Source/WebKit/Shared/mac/WebGestureEvent.cpp index 45d7d7274970..adf962190758 100644 --- a/Source/WebKit/Shared/mac/WebGestureEvent.cpp +++ b/Source/WebKit/Shared/mac/WebGestureEvent.cpp @@ -29,10 +29,30 @@ #if ENABLE(MAC_GESTURE_EVENTS) #include "ArgumentCoders.h" +#include namespace WebKit { -bool WebGestureEvent::isGestureEventType(WebEventType type) const +WTF_MAKE_TZONE_ALLOCATED_IMPL(WebGestureEvent); + +Ref WebGestureEvent::create(WebEventData&& eventData, WebGestureEventData&& gestureData) +{ + return adoptRef(*new WebGestureEvent(WTF::move(eventData), WTF::move(gestureData))); +} + +Ref WebGestureEvent::create(WebGestureEventInit&& init) +{ + return create(WTF::move(init.event), WTF::move(init.gesture)); +} + +WebGestureEvent::WebGestureEvent(WebEventData&& eventData, WebGestureEventData&& gestureData) + : WebEvent(WTF::move(eventData)) + , m_data(WTF::move(gestureData)) +{ + ASSERT(isGestureEventType(type())); +} + +bool WebGestureEvent::isGestureEventType(WebEventType type) { return type == WebEventType::GestureStart || type == WebEventType::GestureChange || type == WebEventType::GestureEnd; } diff --git a/Source/WebKit/Shared/mac/WebGestureEvent.h b/Source/WebKit/Shared/mac/WebGestureEvent.h index 4cb4b8e006fb..db530ffe37fd 100644 --- a/Source/WebKit/Shared/mac/WebGestureEvent.h +++ b/Source/WebKit/Shared/mac/WebGestureEvent.h @@ -42,33 +42,42 @@ class Encoder; namespace WebKit { +// Field order matches WebEvent.serialization.in. +struct WebGestureEventData { + WebCore::IntPoint position; + float gestureScale { 0 }; + float gestureRotation { 0 }; + WebEventPhase phase { WebEventPhase::None }; +}; + +struct WebGestureEventInit { + WebEventData event; + WebGestureEventData gesture; +}; + class WebGestureEvent : public WebEvent { + WTF_MAKE_TZONE_ALLOCATED(WebGestureEvent); public: using Phase = WebEventPhase; - WebGestureEvent(WebEvent&& event, WebCore::IntPoint position, float gestureScale, float gestureRotation, Phase phase) - : WebEvent(WTF::move(event)) - , m_position(position) - , m_gestureScale(gestureScale) - , m_gestureRotation(gestureRotation) - , m_phase(phase) - { - ASSERT(isGestureEventType(type())); - } + static Ref create(WebEventData&&, WebGestureEventData&&); + static Ref create(WebGestureEventInit&&); + + WebCore::IntPoint position() const { return m_data.position; } + + float gestureScale() const { return m_data.gestureScale; } + float gestureRotation() const { return m_data.gestureRotation; } + Phase phase() const { return m_data.phase; } - WebCore::IntPoint position() const { return m_position; } + const WebGestureEventData& gestureData() const LIFETIME_BOUND { return m_data; } - float gestureScale() const { return m_gestureScale; } - float gestureRotation() const { return m_gestureRotation; } - Phase phase() const { return m_phase; } +protected: + WebGestureEvent(WebEventData&&, WebGestureEventData&&); private: - bool isGestureEventType(WebEventType) const; + static bool isGestureEventType(WebEventType); - WebCore::IntPoint m_position; - float m_gestureScale; - float m_gestureRotation; - Phase m_phase; + WebGestureEventData m_data; }; } // namespace WebKit diff --git a/Source/WebKit/Shared/win/NativeWebKeyboardEventWin.cpp b/Source/WebKit/Shared/win/NativeWebKeyboardEventWin.cpp index f8f5324dd63d..96628c25a020 100644 --- a/Source/WebKit/Shared/win/NativeWebKeyboardEventWin.cpp +++ b/Source/WebKit/Shared/win/NativeWebKeyboardEventWin.cpp @@ -28,14 +28,23 @@ #include "NativeWebKeyboardEvent.h" #include "WebEventFactory.h" +#include namespace WebKit { using namespace WebCore; -NativeWebKeyboardEvent::NativeWebKeyboardEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, Vector&& pendingCharEvents) - : WebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(hwnd, message, wParam, lParam)) - , m_nativeEvent(createNativeEvent(hwnd, message, wParam, lParam)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebKeyboardEvent); + +Ref NativeWebKeyboardEvent::create(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, Vector&& pendingCharEvents) +{ + return adoptRef(*new NativeWebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(hwnd, message, wParam, lParam), + createNativeEvent(hwnd, message, wParam, lParam), WTF::move(pendingCharEvents))); +} + +NativeWebKeyboardEvent::NativeWebKeyboardEvent(WebKeyboardEventInit&& init, const MSG& nativeEvent, Vector&& pendingCharEvents) + : WebKeyboardEvent(WTF::move(init.event), WTF::move(init.keyboard)) + , m_nativeEvent(nativeEvent) , m_pendingCharEvents(WTF::move(pendingCharEvents)) { } diff --git a/Source/WebKit/Shared/win/NativeWebMouseEventWin.cpp b/Source/WebKit/Shared/win/NativeWebMouseEventWin.cpp index 3618a7e8d59f..3dc16138df1d 100644 --- a/Source/WebKit/Shared/win/NativeWebMouseEventWin.cpp +++ b/Source/WebKit/Shared/win/NativeWebMouseEventWin.cpp @@ -28,12 +28,21 @@ #include "NativeWebMouseEvent.h" #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebMouseEvent::NativeWebMouseEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, bool didActivateWebView, float deviceScaleFactor) - : WebMouseEvent(WebEventFactory::createWebMouseEvent(hwnd, message, wParam, lParam, didActivateWebView, deviceScaleFactor)) - , m_nativeEvent(createNativeEvent(hwnd, message, wParam, lParam)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebMouseEvent); + +Ref NativeWebMouseEvent::create(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, bool didActivateWebView, float deviceScaleFactor) +{ + return adoptRef(*new NativeWebMouseEvent(WebEventFactory::createWebMouseEvent(hwnd, message, wParam, lParam, didActivateWebView, deviceScaleFactor), + createNativeEvent(hwnd, message, wParam, lParam))); +} + +NativeWebMouseEvent::NativeWebMouseEvent(WebMouseEventInit&& init, const MSG& nativeEvent) + : WebMouseEvent(WTF::move(init.event), WTF::move(init.mouse)) + , m_nativeEvent(nativeEvent) { } diff --git a/Source/WebKit/Shared/win/NativeWebTouchEventWin.cpp b/Source/WebKit/Shared/win/NativeWebTouchEventWin.cpp index 1dab9f2b3fce..7e02c161cf82 100644 --- a/Source/WebKit/Shared/win/NativeWebTouchEventWin.cpp +++ b/Source/WebKit/Shared/win/NativeWebTouchEventWin.cpp @@ -30,11 +30,19 @@ #if ENABLE(TOUCH_EVENTS) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebTouchEvent::NativeWebTouchEvent() - : WebTouchEvent(WebEventFactory::createWebTouchEvent()) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebTouchEvent); + +Ref NativeWebTouchEvent::create() +{ + return adoptRef(*new NativeWebTouchEvent(WebEventFactory::createWebTouchEvent())); +} + +NativeWebTouchEvent::NativeWebTouchEvent(WebTouchEventInit&& init) + : WebTouchEvent(WTF::move(init.event), WTF::move(init.touch)) { } diff --git a/Source/WebKit/Shared/win/NativeWebWheelEventWin.cpp b/Source/WebKit/Shared/win/NativeWebWheelEventWin.cpp index ade64b0e3f15..3cb8f861358b 100644 --- a/Source/WebKit/Shared/win/NativeWebWheelEventWin.cpp +++ b/Source/WebKit/Shared/win/NativeWebWheelEventWin.cpp @@ -28,12 +28,21 @@ #include "NativeWebWheelEvent.h" #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebWheelEvent::NativeWebWheelEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, float deviceScaleFactor) - : WebWheelEvent(WebEventFactory::createWebWheelEvent(hwnd, message, wParam, lParam, deviceScaleFactor)) - , m_nativeEvent(createNativeEvent(hwnd, message, wParam, lParam)) +WTF_MAKE_TZONE_ALLOCATED_IMPL(NativeWebWheelEvent); + +Ref NativeWebWheelEvent::create(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, float deviceScaleFactor) +{ + return adoptRef(*new NativeWebWheelEvent(WebEventFactory::createWebWheelEvent(hwnd, message, wParam, lParam, deviceScaleFactor), + createNativeEvent(hwnd, message, wParam, lParam))); +} + +NativeWebWheelEvent::NativeWebWheelEvent(WebWheelEventInit&& init, const MSG& nativeEvent) + : WebWheelEvent(WTF::move(init.event), WTF::move(init.wheel)) + , m_nativeEvent(nativeEvent) { } diff --git a/Source/WebKit/Shared/win/WebEventFactory.cpp b/Source/WebKit/Shared/win/WebEventFactory.cpp index 5c6be67ada8c..6b1e14f977dd 100644 --- a/Source/WebKit/Shared/win/WebEventFactory.cpp +++ b/Source/WebKit/Shared/win/WebEventFactory.cpp @@ -344,7 +344,7 @@ static String keyIdentifierFromEvent(WPARAM wparam, WebEventType type) } } -WebMouseEvent WebEventFactory::createWebMouseEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool didActivateWebView, float deviceScaleFactor) +WebMouseEventInit WebEventFactory::createWebMouseEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool didActivateWebView, float deviceScaleFactor) { WebEventType type; WebMouseEventButton button = WebMouseEventButton::None; @@ -417,10 +417,32 @@ WebMouseEvent WebEventFactory::createWebMouseEvent(HWND hWnd, UINT message, WPAR auto modifiers = modifiersForEvent(wParam); auto buttons = buttonsForEvent(wParam); - return WebMouseEvent( { type, modifiers, MonotonicTime::now() }, button, buttons, flooredIntPoint(position), flooredIntPoint(globalPosition), 0, 0, 0, clickCount, didActivateWebView, WebEventInputSource::UserDriven); + // NOTE: didActivateWebView is deliberately passed as the force value, preserving prior behavior. + return { + { type, modifiers, MonotonicTime::now() }, + { + .button = button, + .buttons = buttons, + .position = flooredIntPoint(position), + .globalPosition = flooredIntPoint(globalPosition), + .deltaX = 0, + .deltaY = 0, + .deltaZ = 0, + .clickCount = clickCount, + .force = static_cast(didActivateWebView), + .inputSource = WebEventInputSource::UserDriven, + .canInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, + .syntheticClickType = WebMouseEventSyntheticClickType::NoTap, + .pointerId = WebCore::mousePointerID, + .pointerType = WebCore::mousePointerEventType(), + .gestureWasCancelled = GestureWasCancelled::No, + .unadjustedMovementDelta = { }, + .coalescedEvents = { }, + } + }; } -WebWheelEvent WebEventFactory::createWebWheelEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, float deviceScaleFactor) +WebWheelEventInit WebEventFactory::createWebWheelEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, float deviceScaleFactor) { POINT positionPoint = point(lParam); FloatPoint globalPosition = positionPoint; @@ -464,10 +486,19 @@ WebWheelEvent WebEventFactory::createWebWheelEvent(HWND hWnd, UINT message, WPAR } } - return WebWheelEvent( { WebEventType::Wheel, modifiers, MonotonicTime::now() }, flooredIntPoint(position), flooredIntPoint(globalPosition), FloatSize(deltaX, deltaY), FloatSize(wheelTicksX, wheelTicksY), granularity); + return { + { WebEventType::Wheel, modifiers, MonotonicTime::now() }, + { + .position = flooredIntPoint(position), + .globalPosition = flooredIntPoint(globalPosition), + .delta = FloatSize(deltaX, deltaY), + .wheelTicks = FloatSize(wheelTicksX, wheelTicksY), + .granularity = granularity, + } + }; } -WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +WebKeyboardEventInit WebEventFactory::createWebKeyboardEvent(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { auto type = keyboardEventTypeForEvent(message); String text = textFromEvent(wparam, type); @@ -483,13 +514,28 @@ WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(HWND hwnd, UINT message bool isSystemKey = isSystemKeyEvent(message); auto modifiers = modifiersForCurrentKeyState(windowsKeyNames().shouldExposeAltGraphForKeyEvent(message, wparam, lparam)); - return WebKeyboardEvent( { type, modifiers, MonotonicTime::now() }, text, unmodifiedText, key, code, keyIdentifier, windowsVirtualKeyCode, nativeVirtualKeyCode, macCharCode, autoRepeat, isKeypad, isSystemKey); + return { + { type, modifiers, MonotonicTime::now() }, + { + .text = text, + .unmodifiedText = unmodifiedText, + .key = key, + .code = code, + .keyIdentifier = keyIdentifier, + .windowsVirtualKeyCode = windowsVirtualKeyCode, + .nativeVirtualKeyCode = nativeVirtualKeyCode, + .macCharCode = macCharCode, + .isAutoRepeat = autoRepeat, + .isKeypad = isKeypad, + .isSystemKey = isSystemKey, + } + }; } #if ENABLE(TOUCH_EVENTS) -WebTouchEvent WebEventFactory::createWebTouchEvent() +WebTouchEventInit WebEventFactory::createWebTouchEvent() { - return WebTouchEvent({ WebEventType::TouchMove, OptionSet { }, MonotonicTime::now() }, { }, { }, { }); + return { { WebEventType::TouchMove, OptionSet { }, MonotonicTime::now() }, { } }; } #endif // ENABLE(TOUCH_EVENTS) diff --git a/Source/WebKit/Shared/win/WebEventFactory.h b/Source/WebKit/Shared/win/WebEventFactory.h index e2dd820ae551..d84c86947afc 100644 --- a/Source/WebKit/Shared/win/WebEventFactory.h +++ b/Source/WebKit/Shared/win/WebEventFactory.h @@ -40,11 +40,11 @@ namespace WebKit { class WebEventFactory { public: - static WebMouseEvent createWebMouseEvent(HWND, UINT message, WPARAM, LPARAM, bool didActivateWebView, float deviceScaleFactor); - static WebWheelEvent createWebWheelEvent(HWND, UINT message, WPARAM, LPARAM, float deviceScaleFactor); - static WebKeyboardEvent createWebKeyboardEvent(HWND, UINT message, WPARAM, LPARAM); + static WebMouseEventInit createWebMouseEvent(HWND, UINT message, WPARAM, LPARAM, bool didActivateWebView, float deviceScaleFactor); + static WebWheelEventInit createWebWheelEvent(HWND, UINT message, WPARAM, LPARAM, float deviceScaleFactor); + static WebKeyboardEventInit createWebKeyboardEvent(HWND, UINT message, WPARAM, LPARAM); #if ENABLE(TOUCH_EVENTS) - static WebTouchEvent createWebTouchEvent(); + static WebTouchEventInit createWebTouchEvent(); #endif }; diff --git a/Source/WebKit/Shared/wpe/NativeWebKeyboardEventWPE.cpp b/Source/WebKit/Shared/wpe/NativeWebKeyboardEventWPE.cpp index a960c5b06f24..5e25226540a3 100644 --- a/Source/WebKit/Shared/wpe/NativeWebKeyboardEventWPE.cpp +++ b/Source/WebKit/Shared/wpe/NativeWebKeyboardEventWPE.cpp @@ -29,17 +29,33 @@ #if ENABLE(WPE_PLATFORM) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebKeyboardEvent::NativeWebKeyboardEvent(WPEEvent* event, const String& text, bool isAutorepeat) - : WebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, text, isAutorepeat)) +Ref NativeWebKeyboardEvent::create(WPEEvent* event, const String& text, bool isAutorepeat) { + return adoptRef(*new NativeWebKeyboardEvent(WebEventFactory::createWebKeyboardEvent(event, text, isAutorepeat))); } -NativeWebKeyboardEvent::NativeWebKeyboardEvent(const String& text, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) - : WebKeyboardEvent(WebEvent(WebEventType::KeyDown, { }, MonotonicTime::now()), text, "Unidentified"_s, "Unidentified"_s, "U+0000"_s, 0, 0, true, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange), false, false) +Ref NativeWebKeyboardEvent::create(const String& text, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange) { + return adoptRef(*new NativeWebKeyboardEvent(WebKeyboardEventInit { + { WebEventType::KeyDown, { }, MonotonicTime::now() }, + { + .text = text, + .key = "Unidentified"_s, + .code = "Unidentified"_s, + .keyIdentifier = "U+0000"_s, + .windowsVirtualKeyCode = 0, + .nativeVirtualKeyCode = 0, + .handledByInputMethod = true, + .preeditUnderlines = WTF::move(preeditUnderlines), + .preeditSelectionRange = WTF::move(preeditSelectionRange), + .isAutoRepeat = false, + .isKeypad = false, + } + })); } } // namespace WebKit diff --git a/Source/WebKit/Shared/wpe/NativeWebMouseEventWPE.cpp b/Source/WebKit/Shared/wpe/NativeWebMouseEventWPE.cpp index 9fcd90c5e59b..4769ed741c29 100644 --- a/Source/WebKit/Shared/wpe/NativeWebMouseEventWPE.cpp +++ b/Source/WebKit/Shared/wpe/NativeWebMouseEventWPE.cpp @@ -29,12 +29,13 @@ #if ENABLE(WPE_PLATFORM) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebMouseEvent::NativeWebMouseEvent(WPEEvent* event) - : WebMouseEvent(WebEventFactory::createWebMouseEvent(event)) +Ref NativeWebMouseEvent::create(WPEEvent* event) { + return adoptRef(*new NativeWebMouseEvent(WebEventFactory::createWebMouseEvent(event))); } } // namespace WebKit diff --git a/Source/WebKit/Shared/wpe/NativeWebTouchEventWPE.cpp b/Source/WebKit/Shared/wpe/NativeWebTouchEventWPE.cpp index e20403ebbc51..e69ced744a3f 100644 --- a/Source/WebKit/Shared/wpe/NativeWebTouchEventWPE.cpp +++ b/Source/WebKit/Shared/wpe/NativeWebTouchEventWPE.cpp @@ -29,11 +29,17 @@ #if ENABLE(TOUCH_EVENTS) && ENABLE(WPE_PLATFORM) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebTouchEvent::NativeWebTouchEvent(WPEEvent* event, Vector&& touchPoints) - : WebTouchEvent(WebEventFactory::createWebTouchEvent(event, WTF::move(touchPoints))) +Ref NativeWebTouchEvent::create(WPEEvent* event, Vector&& touchPoints) +{ + return adoptRef(*new NativeWebTouchEvent(WebEventFactory::createWebTouchEvent(event, WTF::move(touchPoints)), event)); +} + +NativeWebTouchEvent::NativeWebTouchEvent(WebTouchEventInit&& init, WPEEvent* event) + : WebTouchEvent(WTF::move(init.event), WTF::move(init.touch)) , m_nativeEvent(event) { } diff --git a/Source/WebKit/Shared/wpe/NativeWebWheelEventWPE.cpp b/Source/WebKit/Shared/wpe/NativeWebWheelEventWPE.cpp index 689898a7c5c3..dc418976e141 100644 --- a/Source/WebKit/Shared/wpe/NativeWebWheelEventWPE.cpp +++ b/Source/WebKit/Shared/wpe/NativeWebWheelEventWPE.cpp @@ -29,17 +29,18 @@ #if ENABLE(WPE_PLATFORM) #include "WebEventFactory.h" +#include namespace WebKit { -NativeWebWheelEvent::NativeWebWheelEvent(WPEEvent* event) - : WebWheelEvent(WebEventFactory::createWebWheelEvent(event)) +Ref NativeWebWheelEvent::create(WPEEvent* event) { + return adoptRef(*new NativeWebWheelEvent(WebEventFactory::createWebWheelEvent(event))); } -NativeWebWheelEvent::NativeWebWheelEvent(WPEEvent* event, WebWheelEvent::Phase phase) - : WebWheelEvent(WebEventFactory::createWebWheelEvent(event, phase)) +Ref NativeWebWheelEvent::create(WPEEvent* event, WebWheelEvent::Phase phase) { + return adoptRef(*new NativeWebWheelEvent(WebEventFactory::createWebWheelEvent(event, phase))); } } // namespace WebKit diff --git a/Source/WebKit/Shared/wpe/WebEventFactoryWPE.cpp b/Source/WebKit/Shared/wpe/WebEventFactoryWPE.cpp index 0fe8d9a3e6e6..a792186daec7 100644 --- a/Source/WebKit/Shared/wpe/WebEventFactoryWPE.cpp +++ b/Source/WebKit/Shared/wpe/WebEventFactoryWPE.cpp @@ -125,7 +125,7 @@ static IntPoint positionFromEvent(WPEEvent* event) return { }; } -WebMouseEvent WebEventFactory::createWebMouseEvent(WPEEvent* event) +WebMouseEventInit WebEventFactory::createWebMouseEvent(WPEEvent* event) { auto modifiers = wpe_event_get_modifiers(event); FloatPoint movementDelta; @@ -160,28 +160,37 @@ WebMouseEvent WebEventFactory::createWebMouseEvent(WPEEvent* event) RELEASE_ASSERT_NOT_REACHED(); } - return WebMouseEvent({ type.value(), modifiersFromWPEModifiers(modifiers), monotonicTimeForEvent(event) }, - button, - pressedMouseButtons(modifiers), - position, - position, - movementDelta.x(), - movementDelta.y(), - 0 /* deltaZ */, - clickCount, - 0 /* force */, - WebEventInputSource::UserDriven, - WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, - syntheticClickType); + return { + { type.value(), modifiersFromWPEModifiers(modifiers), monotonicTimeForEvent(event) }, + { + .button = button, + .buttons = static_cast(pressedMouseButtons(modifiers)), + .position = position, + .globalPosition = position, + .deltaX = static_cast(movementDelta.x()), + .deltaY = static_cast(movementDelta.y()), + .deltaZ = 0, + .clickCount = static_cast(clickCount), + .force = 0, + .inputSource = WebEventInputSource::UserDriven, + .canInitiateDrag = WebCore::PlatformMouseEvent::CanInitiateDrag::Yes, + .syntheticClickType = syntheticClickType, + .pointerId = WebCore::mousePointerID, + .pointerType = WebCore::mousePointerEventType(), + .gestureWasCancelled = GestureWasCancelled::No, + .unadjustedMovementDelta = { }, + .coalescedEvents = { }, + } + }; } -WebWheelEvent WebEventFactory::createWebWheelEvent(WPEEvent* event) +WebWheelEventInit WebEventFactory::createWebWheelEvent(WPEEvent* event) { auto phase = wpe_event_scroll_is_stop(event) ? WebWheelEvent::Phase::Ended : WebWheelEvent::Phase::Changed; return createWebWheelEvent(event, phase); } -WebWheelEvent WebEventFactory::createWebWheelEvent(WPEEvent* event, WebWheelEvent::Phase phase) +WebWheelEventInit WebEventFactory::createWebWheelEvent(WPEEvent* event, WebWheelEvent::Phase phase) { double deltaX, deltaY; wpe_event_scroll_get_deltas(event, &deltaX, &deltaY); @@ -217,27 +226,46 @@ WebWheelEvent WebEventFactory::createWebWheelEvent(WPEEvent* event, WebWheelEven delta = wheelTicks.scaled(stepX, stepY); } - return WebWheelEvent({ WebEventType::Wheel, modifiersFromWPEModifiers(wpe_event_get_modifiers(event)), monotonicTimeForEvent(event) }, - position, position, delta, wheelTicks, WebWheelEvent::Granularity::ScrollByPixelWheelEvent, phase, WebWheelEvent::Phase::None, hasPreciseScrollingDeltas); + return { + { WebEventType::Wheel, modifiersFromWPEModifiers(wpe_event_get_modifiers(event)), monotonicTimeForEvent(event) }, + { + .position = position, + .globalPosition = position, + .delta = delta, + .wheelTicks = wheelTicks, + .granularity = WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .phase = phase, + .momentumPhase = WebWheelEvent::Phase::None, + .hasPreciseScrollingDeltas = hasPreciseScrollingDeltas, + } + }; } -WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(WPEEvent* event, const String& text, bool isAutoRepeat) +WebKeyboardEventInit WebEventFactory::createWebKeyboardEvent(WPEEvent* event, const String& text, bool isAutoRepeat) { auto type = wpe_event_get_event_type(event) == WPE_EVENT_KEYBOARD_KEY_DOWN ? WebEventType::KeyDown : WebEventType::KeyUp; auto keyval = wpe_event_keyboard_get_keyval(event); auto keycode = wpe_event_keyboard_get_keycode(event); - return WebKeyboardEvent({ type, modifiersFromWPEModifiers(wpe_event_get_modifiers(event)), monotonicTimeForEvent(event) }, - text.isNull() ? WebKeyboardEvent::singleCharacterStringForWPEKeyval(keyval) : text, - WebKeyboardEvent::keyValueStringForWPEKeyval(keyval), - WebKeyboardEvent::keyCodeStringForWPEKeycode(keycode), - WebKeyboardEvent::keyIdentifierForWPEKeyval(keyval), - WebKeyboardEvent::windowsKeyCodeForWPEKeyval(keyval), - keyval, false, std::nullopt, std::nullopt, isAutoRepeat, - keyval >= WPE_KEY_KP_Space && keyval <= WPE_KEY_KP_9); + return { + { type, modifiersFromWPEModifiers(wpe_event_get_modifiers(event)), monotonicTimeForEvent(event) }, + { + .text = text.isNull() ? WebKeyboardEvent::singleCharacterStringForWPEKeyval(keyval) : text, + .key = WebKeyboardEvent::keyValueStringForWPEKeyval(keyval), + .code = WebKeyboardEvent::keyCodeStringForWPEKeycode(keycode), + .keyIdentifier = WebKeyboardEvent::keyIdentifierForWPEKeyval(keyval), + .windowsVirtualKeyCode = WebKeyboardEvent::windowsKeyCodeForWPEKeyval(keyval), + .nativeVirtualKeyCode = static_cast(keyval), + .handledByInputMethod = false, + .preeditUnderlines = std::nullopt, + .preeditSelectionRange = std::nullopt, + .isAutoRepeat = isAutoRepeat, + .isKeypad = keyval >= WPE_KEY_KP_Space && keyval <= WPE_KEY_KP_9, + } + }; } #if ENABLE(TOUCH_EVENTS) -WebTouchEvent WebEventFactory::createWebTouchEvent(WPEEvent* event, Vector&& touchPoints) +WebTouchEventInit WebEventFactory::createWebTouchEvent(WPEEvent* event, Vector&& touchPoints) { std::optional type; switch (wpe_event_get_event_type(event)) { @@ -257,7 +285,7 @@ WebTouchEvent WebEventFactory::createWebTouchEvent(WPEEvent* event, Vector> preeditUnderlines; std::optional preeditSelectionRange; - WebKit::toImpl(pageRef)->handleKeyboardEvent(NativeWebKeyboardEvent(&wpeEvent, ""_s, false, handledByInputMethod, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange))); + WebKit::toImpl(pageRef)->handleKeyboardEvent(NativeWebKeyboardEvent::create(&wpeEvent, ""_s, false, handledByInputMethod, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange))); } void WKPageHandleMouseEvent(WKPageRef pageRef, WKMouseEvent event) @@ -141,7 +141,7 @@ void WKPageHandleMouseEvent(WKPageRef pageRef, WKMouseEvent event) const float deviceScaleFactor = 1; - WebKit::toImpl(pageRef)->handleMouseEvent(NativeWebMouseEvent(&wpeEvent, deviceScaleFactor)); + WebKit::toImpl(pageRef)->handleMouseEvent(NativeWebMouseEvent::create(&wpeEvent, deviceScaleFactor)); } void WKPageHandleWheelEvent(WKPageRef pageRef, WKWheelEvent event) @@ -159,7 +159,7 @@ void WKPageHandleWheelEvent(WKPageRef pageRef, WKWheelEvent event) 1, static_cast(event.delta.width), 0 }; - WebKit::toImpl(pageRef)->handleNativeWheelEvent(NativeWebWheelEvent(&xEvent, deviceScaleFactor, WebWheelEvent::Phase::None, WebWheelEvent::Phase::None)); + WebKit::toImpl(pageRef)->handleNativeWheelEvent(NativeWebWheelEvent::create(&xEvent, deviceScaleFactor, WebWheelEvent::Phase::None, WebWheelEvent::Phase::None)); struct wpe_input_axis_event yEvent = { wpe_input_axis_event_type_motion, @@ -167,7 +167,7 @@ void WKPageHandleWheelEvent(WKPageRef pageRef, WKWheelEvent event) 0, static_cast(event.delta.height), 0 }; - WebKit::toImpl(pageRef)->handleNativeWheelEvent(NativeWebWheelEvent(&yEvent, deviceScaleFactor, WebWheelEvent::Phase::None, WebWheelEvent::Phase::None)); + WebKit::toImpl(pageRef)->handleNativeWheelEvent(NativeWebWheelEvent::create(&yEvent, deviceScaleFactor, WebWheelEvent::Phase::None, WebWheelEvent::Phase::None)); } void WKPagePaint(WKPageRef pageRef, unsigned char* surfaceData, WKSize wkSurfaceSize, WKRect wkPaintRect) diff --git a/Source/WebKit/UIProcess/API/C/wpe/WKPagePrivateWPE.cpp b/Source/WebKit/UIProcess/API/C/wpe/WKPagePrivateWPE.cpp index 7ad596243b6c..a7fc90a9a54a 100644 --- a/Source/WebKit/UIProcess/API/C/wpe/WKPagePrivateWPE.cpp +++ b/Source/WebKit/UIProcess/API/C/wpe/WKPagePrivateWPE.cpp @@ -64,7 +64,7 @@ void WKPageHandleKeyboardEvent(WKPageRef pageRef, WKKeyboardEvent event) if (auto* view = WebKit::toImpl(pageRef)->wpeView()) { GRefPtr wpeEvent = adoptGRef(wpe_event_keyboard_new(event.type == kWKEventKeyDown ? WPE_EVENT_KEYBOARD_KEY_DOWN : WPE_EVENT_KEYBOARD_KEY_UP, view, WPE_INPUT_SOURCE_KEYBOARD, 0, wkEventModifiersToWPE(event.modifiers), event.hardwareKeyCode, event.keyCode)); - WebKit::toImpl(pageRef)->handleKeyboardEvent(NativeWebKeyboardEvent(wpeEvent.get(), unsafeMakeSpan(event.text, event.length), false)); + WebKit::toImpl(pageRef)->handleKeyboardEvent(NativeWebKeyboardEvent::create(wpeEvent.get(), unsafeMakeSpan(event.text, event.length), false)); return; } #endif @@ -90,7 +90,7 @@ void WKPageHandleKeyboardEvent(WKPageRef pageRef, WKKeyboardEvent event) NativeWebKeyboardEvent::HandledByInputMethod handledByInputMethod = NativeWebKeyboardEvent::HandledByInputMethod::No; std::optional> preeditUnderlines; std::optional preeditSelectionRange; - WebKit::toImpl(pageRef)->handleKeyboardEvent(NativeWebKeyboardEvent(&wpeEvent, unsafeMakeSpan(event.text, event.length), false, handledByInputMethod, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange))); + WebKit::toImpl(pageRef)->handleKeyboardEvent(NativeWebKeyboardEvent::create(&wpeEvent, unsafeMakeSpan(event.text, event.length), false, handledByInputMethod, WTF::move(preeditUnderlines), WTF::move(preeditSelectionRange))); #endif } @@ -130,7 +130,7 @@ void WKPageHandleMouseEvent(WKPageRef pageRef, WKMouseEvent event) break; } - WebKit::toImpl(pageRef)->handleMouseEvent(NativeWebMouseEvent(wpeEvent.get())); + WebKit::toImpl(pageRef)->handleMouseEvent(NativeWebMouseEvent::create(wpeEvent.get())); return; } #endif @@ -180,6 +180,6 @@ void WKPageHandleMouseEvent(WKPageRef pageRef, WKMouseEvent event) const float deviceScaleFactor = 1; - WebKit::toImpl(pageRef)->handleMouseEvent(NativeWebMouseEvent(&wpeEvent, deviceScaleFactor)); + WebKit::toImpl(pageRef)->handleMouseEvent(NativeWebMouseEvent::create(&wpeEvent, deviceScaleFactor)); #endif } diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp index ecfe01bc5646..56edb11f9f64 100644 --- a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp +++ b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp @@ -1197,7 +1197,7 @@ static gboolean webkitWebViewBaseKeyPressEvent(GtkWidget* widget, GdkEventKey* k auto filterResult = priv->inputMethodFilter.filterKeyEvent(reinterpret_cast(keyEvent)); if (!filterResult.handled) { - priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast(keyEvent), filterResult.keyText, isAutoRepeat, + priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent::create(reinterpret_cast(keyEvent), filterResult.keyText, isAutoRepeat, priv->keyBindingTranslator.commandsForKeyEvent(keyEvent))); } @@ -1212,7 +1212,7 @@ static gboolean webkitWebViewBaseKeyReleaseEvent(GtkWidget* widget, GdkEventKey* priv->keyAutoRepeatHandler.keyRelease(); if (!priv->inputMethodFilter.filterKeyEvent(reinterpret_cast(keyEvent)).handled) - priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast(keyEvent), { }, false, { })); + priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent::create(reinterpret_cast(keyEvent), { }, false, { })); return GDK_EVENT_STOP; } @@ -1272,7 +1272,7 @@ static gboolean webkitWebViewBaseKeyPressed(WebKitWebViewBase* webViewBase, unsi auto filterResult = priv->inputMethodFilter.filterKeyEvent(event); if (!filterResult.handled) { - priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(event, filterResult.keyText, isAutoRepeat, + priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent::create(event, filterResult.keyText, isAutoRepeat, priv->keyBindingTranslator.commandsForKeyEvent(GTK_EVENT_CONTROLLER_KEY(controller)))); } @@ -1287,7 +1287,7 @@ static void webkitWebViewBaseKeyReleased(WebKitWebViewBase* webViewBase, unsigne auto* event = gtk_event_controller_get_current_event(controller); if (!priv->inputMethodFilter.filterKeyEvent(event).handled) - priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(event, { }, false, { })); + priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent::create(event, { }, false, { })); } #endif @@ -1342,7 +1342,7 @@ static void webkitWebViewBaseHandleMouseEvent(WebKitWebViewBase* webViewBase, Gd ASSERT_NOT_REACHED(); } - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(event, clickCount, movementDelta)); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(event, clickCount, movementDelta)); } static gboolean webkitWebViewBaseButtonPressEvent(GtkWidget* widget, GdkEventButton* event) @@ -1403,7 +1403,7 @@ static void webkitWebViewBaseButtonPressed(WebKitWebViewBase* webViewBase, int c priv->contextMenuEvent = event; #endif - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(event, DoublePoint(x, y), clickCount, std::nullopt)); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(event, DoublePoint(x, y), clickCount, std::nullopt)); } static void webkitWebViewBaseButtonReleased(WebKitWebViewBase* webViewBase, int clickCount, double x, double y, GtkGesture* gesture) @@ -1419,7 +1419,7 @@ static void webkitWebViewBaseButtonReleased(WebKitWebViewBase* webViewBase, int auto* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture)); gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED); - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(gtk_gesture_get_last_event(gesture, sequence), DoublePoint(x, y), clickCount, std::nullopt)); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(gtk_gesture_get_last_event(gesture, sequence), DoublePoint(x, y), clickCount, std::nullopt)); } #endif @@ -1522,7 +1522,7 @@ static gboolean webkitWebViewBaseScrollEvent(GtkWidget* widget, GdkEventScroll* FloatSize delta = wheelTicks.scaled(stepX, stepY); - priv->pageProxy->handleNativeWheelEvent(NativeWebWheelEvent(event, position, globalPosition, delta, wheelTicks, phase, WebWheelEvent::Phase::None, hasPreciseScrollingDeltas)); + priv->pageProxy->handleNativeWheelEvent(NativeWebWheelEvent::create(event, position, globalPosition, delta, wheelTicks, phase, WebWheelEvent::Phase::None, hasPreciseScrollingDeltas)); return GDK_EVENT_STOP; } @@ -1598,7 +1598,7 @@ static gboolean handleScroll(WebKitWebViewBase* webViewBase, double deltaX, doub delta = wheelTicks.scaled(stepX, stepY); #endif - priv->pageProxy->handleNativeWheelEvent(NativeWebWheelEvent(event, position, position, delta, wheelTicks, phase, WebWheelEvent::Phase::None, hasPreciseScrollingDeltas)); + priv->pageProxy->handleNativeWheelEvent(NativeWebWheelEvent::create(event, position, position, delta, wheelTicks, phase, WebWheelEvent::Phase::None, hasPreciseScrollingDeltas)); return GDK_EVENT_STOP; } @@ -1728,7 +1728,7 @@ static void webkitWebViewBaseEnter(WebKitWebViewBase* webViewBase, double x, dou return; #endif - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(DoublePoint(x, y))); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(DoublePoint(x, y))); } static gboolean webkitWebViewBaseMotion(WebKitWebViewBase* webViewBase, double x, double y, GtkEventController* controller) @@ -1749,7 +1749,7 @@ static gboolean webkitWebViewBaseMotion(WebKitWebViewBase* webViewBase, double x movementDelta = motionEvent.position - priv->lastMotionEvent->position; priv->lastMotionEvent = WTF::move(motionEvent); - webViewBase->priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(event, DoublePoint(x, y), 0, movementDelta)); + webViewBase->priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(event, DoublePoint(x, y), 0, movementDelta)); return GDK_EVENT_PROPAGATE; } @@ -1786,16 +1786,16 @@ static void webkitWebViewBaseLeave(WebKitWebViewBase* webViewBase, GtkEventContr int yDistanceFromBottomEdge = height - previousY; if (previousX <= xDistanceFromRightEdge && previousX <= previousY && previousX <= yDistanceFromBottomEdge) - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(DoublePoint(-1, previousY))); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(DoublePoint(-1, previousY))); else if (xDistanceFromRightEdge <= previousX && xDistanceFromRightEdge <= previousY && xDistanceFromRightEdge <= yDistanceFromBottomEdge) - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(DoublePoint(width, previousY))); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(DoublePoint(width, previousY))); else if (previousY <= previousX && previousY <= xDistanceFromRightEdge && previousY <= yDistanceFromBottomEdge) - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(DoublePoint(previousX, -1))); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(DoublePoint(previousX, -1))); else { ASSERT(yDistanceFromBottomEdge <= previousX); ASSERT(yDistanceFromBottomEdge <= previousY); ASSERT(yDistanceFromBottomEdge <= xDistanceFromRightEdge); - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(DoublePoint(previousX, height))); + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(DoublePoint(previousX, height))); } } #endif @@ -1909,7 +1909,7 @@ static gboolean webkitWebViewBaseTouchEvent(GtkWidget* widget, GdkEventTouch* ev Vector touchPoints; webkitWebViewBaseGetTouchPointsForEvent(webViewBase, touchEvent, touchPoints); - priv->pageProxy->handleTouchEvent(nullptr, NativeWebTouchEvent(reinterpret_cast(event), WTF::move(touchPoints))); + priv->pageProxy->handleTouchEvent(nullptr, NativeWebTouchEvent::create(reinterpret_cast(event), WTF::move(touchPoints))); #if USE(GTK4) return GDK_EVENT_PROPAGATE; @@ -3118,7 +3118,7 @@ WebKitInputMethodContext* webkitWebViewBaseGetInputMethodContext(WebKitWebViewBa void webkitWebViewBaseSynthesizeCompositionKeyPress(WebKitWebViewBase* webViewBase, const String& text, std::optional>&& underlines, std::optional&& selectionRange) { - webViewBase->priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(text, WTF::move(underlines), WTF::move(selectionRange))); + webViewBase->priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent::create(text, WTF::move(underlines), WTF::move(selectionRange))); } static inline OptionSet toWebKitModifiers(unsigned modifiers) @@ -3245,7 +3245,7 @@ void webkitWebViewBaseSynthesizeMouseEvent(WebKitWebViewBase* webViewBase, Mouse break; } - priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(webEventType, webEventButton, webEventButtons, DoublePoint(x, y), + priv->pageProxy->handleMouseEvent(NativeWebMouseEvent::create(webEventType, webEventButton, webEventButtons, DoublePoint(x, y), widgetRootCoords(GTK_WIDGET(webViewBase), x, y), clickCount, toWebKitModifiers(modifiers), movementDelta, primaryPointerForType(pointerType), pointerType.isNull() ? mousePointerEventType() : pointerType, isTouchEvent)); } @@ -3343,7 +3343,7 @@ void webkitWebViewBaseSynthesizeKeyEvent(WebKitWebViewBase* webViewBase, KeyEven auto filterResult = priv->inputMethodFilter.filterKeyEvent(GDK_KEY_PRESS, keyval, keycode, modifiers); if (!filterResult.handled) { - priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent( + priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent::create( WebEventType::KeyDown, filterResult.keyText.isNull() ? WebKeyboardEvent::singleCharacterStringForGdkKeyval(keyval) : filterResult.keyText, WebKeyboardEvent::keyValueStringForGdkKeyval(keyval), @@ -3360,7 +3360,7 @@ void webkitWebViewBaseSynthesizeKeyEvent(WebKitWebViewBase* webViewBase, KeyEven if (type != KeyEventType::Press) { if (!priv->inputMethodFilter.filterKeyEvent(GDK_KEY_RELEASE, keyval, keycode, modifiers).handled) { - priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent( + priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent::create( WebEventType::KeyUp, WebKeyboardEvent::singleCharacterStringForGdkKeyval(keyval), WebKeyboardEvent::keyValueStringForGdkKeyval(keyval), @@ -3415,7 +3415,7 @@ void webkitWebViewBaseSynthesizeWheelEvent(WebKitWebViewBase* webViewBase, const if (!hasPreciseDeltas) delta.scale(static_cast(Scrollbar::pixelsPerLineStep())); - priv->pageProxy->handleNativeWheelEvent(NativeWebWheelEvent(const_cast(event), { x, y }, widgetRootCoords(GTK_WIDGET(webViewBase), x, y), + priv->pageProxy->handleNativeWheelEvent(NativeWebWheelEvent::create(const_cast(event), { x, y }, widgetRootCoords(GTK_WIDGET(webViewBase), x, y), delta, wheelTicks, toWebKitWheelEventPhase(phase), toWebKitWheelEventPhase(momentumPhase), true)); } @@ -3464,7 +3464,7 @@ void webkitWebViewBaseSynthesizeTouchEvent(WebKitWebViewBase* webViewBase, Touch return WebPlatformTouchPoint(point.id, toWebPlatformTouchPointState(point.state), DoublePoint(rootCoords.x(), rootCoords.y()), DoublePoint(point.x, point.y)); }); - priv->pageProxy->handleTouchEvent(nullptr, NativeWebTouchEvent(webEventType, toWebKitModifiers(modifiers), WTF::move(touchPoints))); + priv->pageProxy->handleTouchEvent(nullptr, NativeWebTouchEvent::create(webEventType, toWebKitModifiers(modifiers), WTF::move(touchPoints))); } #endif // ENABLE(TOUCH_EVENTS) diff --git a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm b/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm index 83b76c976258..da559aac1e02 100644 --- a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm +++ b/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm @@ -2277,10 +2277,10 @@ - (void)scrollView:(WKBaseScrollView *)scrollView handleScrollUpdate:(WKBEScroll // this may not be a WKBEScrollViewScrollUpdatePhaseBegin event, nor even necessarily the first WKBEScrollViewScrollUpdatePhaseChanged event. if (!_wheelEventCountInCurrentScrollGesture) overridePhase = WebKit::WebWheelEvent::Phase::Began; - auto event = WebKit::WebIOSEventFactory::createWebWheelEvent(update, _contentView.get(), overridePhase); + Ref event = WebKit::WebWheelEvent::create(WebKit::WebIOSEventFactory::createWebWheelEvent(update, _contentView.get(), overridePhase)); _wheelEventCountInCurrentScrollGesture++; - _page->handleWheelEventWithoutScrolling(event, [weakSelf = WeakObjCPtr(self), strongCompletion = makeBlockPtr(completion), isCancelable, isHandledByDefault](bool defaultPrevented) { + _page->handleWheelEventWithoutScrolling(WTF::move(event), [weakSelf = WeakObjCPtr(self), strongCompletion = makeBlockPtr(completion), isCancelable, isHandledByDefault](bool defaultPrevented) { RetainPtr strongSelf = weakSelf.get(); if (!strongSelf) { if (isCancelable) diff --git a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp index 0d535dca6e42..9bea1ee07f51 100644 --- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp +++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp @@ -287,7 +287,7 @@ void PageClientImpl::doneWithTouchEvent(const WebTouchEvent& touchEvent, bool wa // Mouse motion towards the point of the click. event->type = wpe_input_pointer_event_type_motion; - page.handleMouseEvent(NativeWebMouseEvent(event, page.deviceScaleFactor(), WebMouseEventSyntheticClickType::OneFingerTap)); + page.handleMouseEvent(NativeWebMouseEvent::create(event, page.deviceScaleFactor(), WebMouseEventSyntheticClickType::OneFingerTap)); event->type = wpe_input_pointer_event_type_button; event->button = 1; @@ -295,12 +295,12 @@ void PageClientImpl::doneWithTouchEvent(const WebTouchEvent& touchEvent, bool wa // Mouse down on the point of the click. event->state = 1; event->modifiers |= wpe_input_pointer_modifier_button1; - page.handleMouseEvent(NativeWebMouseEvent(event, page.deviceScaleFactor(), WebMouseEventSyntheticClickType::OneFingerTap)); + page.handleMouseEvent(NativeWebMouseEvent::create(event, page.deviceScaleFactor(), WebMouseEventSyntheticClickType::OneFingerTap)); // Mouse up on the same location. event->state = 0; event->modifiers &= ~wpe_input_pointer_modifier_button1; - page.handleMouseEvent(NativeWebMouseEvent(event, page.deviceScaleFactor(), WebMouseEventSyntheticClickType::OneFingerTap)); + page.handleMouseEvent(NativeWebMouseEvent::create(event, page.deviceScaleFactor(), WebMouseEventSyntheticClickType::OneFingerTap)); }, [&](TouchGestureController::ContextMenuEvent&) { // FIXME: Generate contextmenuevent without accidentally generating mouseup/mousedown events diff --git a/Source/WebKit/UIProcess/API/wpe/WPEWebViewLegacy.cpp b/Source/WebKit/UIProcess/API/wpe/WPEWebViewLegacy.cpp index b12555be041c..7f8b3be85ce8 100644 --- a/Source/WebKit/UIProcess/API/wpe/WPEWebViewLegacy.cpp +++ b/Source/WebKit/UIProcess/API/wpe/WPEWebViewLegacy.cpp @@ -151,12 +151,12 @@ ViewLegacy::ViewLegacy(struct wpe_view_backend* backend, const API::PageConfigur if (event->type == wpe_input_pointer_event_type_button && event->state == 1) view.m_inputMethodFilter.cancelComposition(); auto& page = view.page(); - WebKit::NativeWebMouseEvent mouseEvent(event, page.deviceScaleFactor()); + Ref mouseEvent = WebKit::NativeWebMouseEvent::create(event, page.deviceScaleFactor()); #if ENABLE(DRAG_SUPPORT) if (view.updateDrag(mouseEvent)) return; #endif - page.handleMouseEvent(mouseEvent); + page.handleMouseEvent(WTF::move(mouseEvent)); }, // handle_axis_event [](void* data, struct wpe_input_axis_event* event) @@ -185,7 +185,7 @@ ViewLegacy::ViewLegacy(struct wpe_view_backend* backend, const API::PageConfigur phase = WebWheelEvent::Phase::Ended; auto& page = view.page(); - page.handleNativeWheelEvent(WebKit::NativeWebWheelEvent(event, page.deviceScaleFactor(), phase, momentumPhase)); + page.handleNativeWheelEvent(WebKit::NativeWebWheelEvent::create(event, page.deviceScaleFactor(), phase, momentumPhase)); return; } #endif @@ -207,7 +207,7 @@ ViewLegacy::ViewLegacy(struct wpe_view_backend* backend, const API::PageConfigur if (shouldDispatch) { auto& page = view.page(); - page.handleNativeWheelEvent(WebKit::NativeWebWheelEvent(event, page.deviceScaleFactor(), phase, momentumPhase)); + page.handleNativeWheelEvent(WebKit::NativeWebWheelEvent::create(event, page.deviceScaleFactor(), phase, momentumPhase)); } }, // handle_touch_event @@ -217,7 +217,7 @@ ViewLegacy::ViewLegacy(struct wpe_view_backend* backend, const API::PageConfigur auto& view = *reinterpret_cast(data); auto& page = view.page(); - WebKit::NativeWebTouchEvent touchEvent(event, page.deviceScaleFactor()); + Ref touchEvent = WebKit::NativeWebTouchEvent::create(event, page.deviceScaleFactor()); // If already gesturing axis events, short-cut directly to the controller, // avoiding the usual roundtrip. @@ -225,7 +225,7 @@ ViewLegacy::ViewLegacy(struct wpe_view_backend* backend, const API::PageConfigur if (touchGestureController.gesturedEvent() == TouchGestureController::GesturedEvent::Axis) { bool handledThroughGestureController = false; - auto generatedEvent = touchGestureController.handleEvent(touchEvent.nativeFallbackTouchPoint()); + auto generatedEvent = touchGestureController.handleEvent(touchEvent->nativeFallbackTouchPoint()); WTF::switchOn(generatedEvent, [](TouchGestureController::NoEvent&) { }, [](TouchGestureController::ClickEvent&) { }, @@ -238,14 +238,14 @@ ViewLegacy::ViewLegacy(struct wpe_view_backend* backend, const API::PageConfigur auto* event = &axisEvent.event; #endif if (event->type != wpe_input_axis_event_type_null) { - page.handleNativeWheelEvent(WebKit::NativeWebWheelEvent(event, page.deviceScaleFactor(), + page.handleNativeWheelEvent(WebKit::NativeWebWheelEvent::create(event, page.deviceScaleFactor(), axisEvent.phase, WebWheelEvent::Phase::None)); handledThroughGestureController = true; } }); } - page.handleTouchEvent(nullptr, touchEvent); + page.handleTouchEvent(nullptr, WTF::move(touchEvent)); #endif }, // padding @@ -347,7 +347,7 @@ void ViewLegacy::handleKeyboardEvent(struct wpe_input_keyboard_event* event) if (filterResult.handled) return; - page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(event, event->pressed ? filterResult.keyText : String(), isAutoRepeat, NativeWebKeyboardEvent::HandledByInputMethod::No, std::nullopt, std::nullopt)); + page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(event, event->pressed ? filterResult.keyText : String(), isAutoRepeat, NativeWebKeyboardEvent::HandledByInputMethod::No, std::nullopt, std::nullopt)); } void ViewLegacy::synthesizeCompositionKeyPress(const String& text, std::optional>&& underlines, std::optional&& selectionRange) @@ -357,7 +357,7 @@ void ViewLegacy::synthesizeCompositionKeyPress(const String& text, std::optional // composition results. WPE doesn't have an equivalent, so we send VoidSymbol // here to WebCore. PlatformKeyEvent converts this code into VK_PROCESSKEY. static struct wpe_input_keyboard_event event = { 0, WPE_KEY_VoidSymbol, 0, true, 0 }; - page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(&event, text, false, NativeWebKeyboardEvent::HandledByInputMethod::Yes, WTF::move(underlines), WTF::move(selectionRange))); + page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(&event, text, false, NativeWebKeyboardEvent::HandledByInputMethod::Yes, WTF::move(underlines), WTF::move(selectionRange))); } #if ENABLE(FULLSCREEN_API) diff --git a/Source/WebKit/UIProcess/API/wpe/WPEWebViewPlatform.cpp b/Source/WebKit/UIProcess/API/wpe/WPEWebViewPlatform.cpp index dff7c5a47202..bd6f9a81e66d 100644 --- a/Source/WebKit/UIProcess/API/wpe/WPEWebViewPlatform.cpp +++ b/Source/WebKit/UIProcess/API/wpe/WPEWebViewPlatform.cpp @@ -398,7 +398,7 @@ gboolean ViewPlatform::handleEvent(WPEEvent* event) { #if ENABLE(TOUCH_EVENTS) && ENABLE(DEVELOPER_MODE) if (wpeEventIsTouchForTesting(event)) { - page().handleTouchEvent(nullptr, NativeWebTouchEvent(event, platformTouchPoints(wpeEventTouchPointsForTesting(event)))); + page().handleTouchEvent(nullptr, NativeWebTouchEvent::create(event, platformTouchPoints(wpeEventTouchPointsForTesting(event)))); return TRUE; } #endif @@ -414,16 +414,16 @@ gboolean ViewPlatform::handleEvent(WPEEvent* event) case WPE_EVENT_POINTER_MOVE: case WPE_EVENT_POINTER_ENTER: case WPE_EVENT_POINTER_LEAVE: { - WebKit::NativeWebMouseEvent mouseEvent(event); + Ref mouseEvent = WebKit::NativeWebMouseEvent::create(event); #if ENABLE(DRAG_SUPPORT) if (updateDrag(mouseEvent)) return TRUE; #endif - page().handleMouseEvent(mouseEvent); + page().handleMouseEvent(WTF::move(mouseEvent)); return TRUE; } case WPE_EVENT_SCROLL: - page().handleNativeWheelEvent(WebKit::NativeWebWheelEvent(event)); + page().handleNativeWheelEvent(WebKit::NativeWebWheelEvent::create(event)); return TRUE; case WPE_EVENT_KEYBOARD_KEY_DOWN: { auto modifiers = wpe_event_get_modifiers(event); @@ -435,20 +435,20 @@ gboolean ViewPlatform::handleEvent(WPEEvent* event) } auto filterResult = m_inputMethodFilter.filterKeyEvent(event); if (!filterResult.handled) - page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(event, filterResult.keyText, m_keyAutoRepeatHandler.keyPress(wpe_event_keyboard_get_keycode(event)))); + page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(event, filterResult.keyText, m_keyAutoRepeatHandler.keyPress(wpe_event_keyboard_get_keycode(event)))); return TRUE; } case WPE_EVENT_KEYBOARD_KEY_UP: { m_keyAutoRepeatHandler.keyRelease(); auto filterResult = m_inputMethodFilter.filterKeyEvent(event); if (!filterResult.handled) - page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(event, String(), false)); + page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(event, String(), false)); return TRUE; } case WPE_EVENT_TOUCH_DOWN: #if ENABLE(TOUCH_EVENTS) m_touchEvents.set(wpe_event_touch_get_sequence_id(event), event); - page().handleTouchEvent(nullptr, NativeWebTouchEvent(event, touchPointsForEvent(event))); + page().handleTouchEvent(nullptr, NativeWebTouchEvent::create(event, touchPointsForEvent(event))); #endif return TRUE; case WPE_EVENT_TOUCH_UP: @@ -457,14 +457,14 @@ gboolean ViewPlatform::handleEvent(WPEEvent* event) m_touchEvents.set(wpe_event_touch_get_sequence_id(event), event); auto points = touchPointsForEvent(event); m_touchEvents.remove(wpe_event_touch_get_sequence_id(event)); - page().handleTouchEvent(nullptr, NativeWebTouchEvent(event, WTF::move(points))); + page().handleTouchEvent(nullptr, NativeWebTouchEvent::create(event, WTF::move(points))); #endif return TRUE; } case WPE_EVENT_TOUCH_MOVE: #if ENABLE(TOUCH_EVENTS) m_touchEvents.set(wpe_event_touch_get_sequence_id(event), event); - page().handleTouchEvent(nullptr, NativeWebTouchEvent(event, touchPointsForEvent(event))); + page().handleTouchEvent(nullptr, NativeWebTouchEvent::create(event, touchPointsForEvent(event))); #endif return TRUE; }; @@ -495,7 +495,7 @@ void ViewPlatform::handleGesture(WPEEvent* event) GRefPtr simulatedEvent = adoptGRef(wpe_event_pointer_move_new( WPE_EVENT_POINTER_MOVE, m_wpeView.get(), WPE_INPUT_SOURCE_TOUCHSCREEN, 0, static_cast(0), x, y, 0, 0 )); - page().handleMouseEvent(WebKit::NativeWebMouseEvent(simulatedEvent.get())); + page().handleMouseEvent(WebKit::NativeWebMouseEvent::create(simulatedEvent.get())); } // Mouse down on the point of the click. @@ -503,7 +503,7 @@ void ViewPlatform::handleGesture(WPEEvent* event) GRefPtr simulatedEvent = adoptGRef(wpe_event_pointer_button_new( WPE_EVENT_POINTER_DOWN, m_wpeView.get(), WPE_INPUT_SOURCE_TOUCHSCREEN, 0, WPE_MODIFIER_POINTER_BUTTON1, 1, x, y, 1 )); - page().handleMouseEvent(WebKit::NativeWebMouseEvent(simulatedEvent.get())); + page().handleMouseEvent(WebKit::NativeWebMouseEvent::create(simulatedEvent.get())); } wpe_view_focus_in(m_wpeView.get()); @@ -513,7 +513,7 @@ void ViewPlatform::handleGesture(WPEEvent* event) GRefPtr simulatedEvent = adoptGRef(wpe_event_pointer_button_new( WPE_EVENT_POINTER_UP, m_wpeView.get(), WPE_INPUT_SOURCE_TOUCHSCREEN, 0, static_cast(0), 1, x, y, 0 )); - page().handleMouseEvent(WebKit::NativeWebMouseEvent(simulatedEvent.get())); + page().handleMouseEvent(WebKit::NativeWebMouseEvent::create(simulatedEvent.get())); } } break; @@ -532,14 +532,14 @@ void ViewPlatform::handleGesture(WPEEvent* event) GRefPtr simulatedScrollEvent = adoptGRef(wpe_event_scroll_new( m_wpeView.get(), WPE_INPUT_SOURCE_TOUCHSCREEN, 0, static_cast(0), dx, dy, TRUE, FALSE, x, y )); - page().handleNativeWheelEvent(WebKit::NativeWebWheelEvent(simulatedScrollEvent.get(), phase)); + page().handleNativeWheelEvent(WebKit::NativeWebWheelEvent::create(simulatedScrollEvent.get(), phase)); } } } void ViewPlatform::synthesizeCompositionKeyPress(const String& text, std::optional>&& underlines, std::optional&& selectionRange) { - page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(text, WTF::move(underlines), WTF::move(selectionRange))); + page().handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(text, WTF::move(underlines), WTF::move(selectionRange))); } void ViewPlatform::setCursor(const WebCore::Cursor& cursor) diff --git a/Source/WebKit/UIProcess/Automation/ios/WebAutomationSessionIOS.mm b/Source/WebKit/UIProcess/Automation/ios/WebAutomationSessionIOS.mm index 9256ab2aafd9..740fd8b2b653 100644 --- a/Source/WebKit/UIProcess/Automation/ios/WebAutomationSessionIOS.mm +++ b/Source/WebKit/UIProcess/Automation/ios/WebAutomationSessionIOS.mm @@ -60,7 +60,7 @@ case WebEventKeyDown: case WebEventKeyUp: - page.handleKeyboardEvent(NativeWebKeyboardEvent(event, NativeWebKeyboardEvent::HandledByInputMethod::No)); + page.handleKeyboardEvent(NativeWebKeyboardEvent::create(event, NativeWebKeyboardEvent::HandledByInputMethod::No)); break; } } diff --git a/Source/WebKit/UIProcess/Automation/win/WebAutomationSessionWin.cpp b/Source/WebKit/UIProcess/Automation/win/WebAutomationSessionWin.cpp index b0c024f61251..ab9a7c2e4e27 100644 --- a/Source/WebKit/UIProcess/Automation/win/WebAutomationSessionWin.cpp +++ b/Source/WebKit/UIProcess/Automation/win/WebAutomationSessionWin.cpp @@ -110,7 +110,7 @@ static void doMouseButtonEvent(WebPageProxy& page, MouseInteraction interaction, } auto hwnd = reinterpret_cast(page.viewWidget()); - page.handleMouseEvent(NativeWebMouseEvent(hwnd, message, wparam, lparam, { }, page.deviceScaleFactor())); + page.handleMouseEvent(NativeWebMouseEvent::create(hwnd, message, wparam, lparam, { }, page.deviceScaleFactor())); } void WebAutomationSession::platformSimulateMouseInteraction(WebPageProxy& page, MouseInteraction interaction, MouseButton button, const WebCore::IntPoint& locationInView, OptionSet keyModifiers, const String& pointerType) @@ -329,8 +329,8 @@ void WebAutomationSession::platformSimulateKeyboardInteraction(WebPageProxy& pag } auto hwnd = reinterpret_cast(page.viewWidget()); - NativeWebKeyboardEvent event(hwnd, message, wparam, lparam, { }); - page.handleKeyboardEvent(event); + Ref event = NativeWebKeyboardEvent::create(hwnd, message, wparam, lparam, { }); + page.handleKeyboardEvent(WTF::move(event)); } OptionSet WebAutomationSession::platformWebModifiersFromRaw(WebPageProxy&, unsigned modifiers) @@ -359,8 +359,8 @@ void WebAutomationSession::platformSimulateKeySequence(WebPageProxy& page, const // If we need that information, the 4th argument should be set to an appropriate value, not 0. // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-char // https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags - NativeWebKeyboardEvent event(hwnd, WM_CHAR, keySequence.codeUnitAt(0), 0, { }); - page.handleKeyboardEvent(event); + Ref event = NativeWebKeyboardEvent::create(hwnd, WM_CHAR, keySequence.codeUnitAt(0), 0, { }); + page.handleKeyboardEvent(WTF::move(event)); } } // namespace WebKit diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp index 44ac17408dc0..c01fe3857dc0 100644 --- a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp +++ b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp @@ -219,7 +219,7 @@ void RemoteScrollingCoordinatorProxy::stickyScrollingTreeNodeBeganSticking(Scrol protect(webPageProxy())->stickyScrollingTreeNodeBeganSticking(); } -void RemoteScrollingCoordinatorProxy::handleWheelEvent(const WebWheelEvent& wheelEvent, RectEdges rubberBandableEdges) +void RemoteScrollingCoordinatorProxy::handleWheelEvent(Ref&& wheelEvent, RectEdges rubberBandableEdges) { #if !(PLATFORM(MAC) && ENABLE(UI_SIDE_COMPOSITING)) auto platformWheelEvent = platform(wheelEvent); @@ -231,7 +231,7 @@ void RemoteScrollingCoordinatorProxy::handleWheelEvent(const WebWheelEvent& whee auto processingSteps = m_scrollingTree->determineWheelEventProcessing(platformWheelEvent); if (!processingSteps.contains(WheelEventProcessingSteps::AsyncScrolling)) { - continueWheelEventHandling(wheelEvent, { processingSteps, false }); + continueWheelEventHandling(WTF::move(wheelEvent), { processingSteps, false }); return; } @@ -241,17 +241,17 @@ void RemoteScrollingCoordinatorProxy::handleWheelEvent(const WebWheelEvent& whee auto result = m_scrollingTree->handleWheelEvent(filteredEvent, processingSteps); didReceiveWheelEvent(result.wasHandled); - continueWheelEventHandling(wheelEvent, result); + continueWheelEventHandling(WTF::move(wheelEvent), result); #else UNUSED_PARAM(wheelEvent); UNUSED_PARAM(rubberBandableEdges); #endif } -void RemoteScrollingCoordinatorProxy::continueWheelEventHandling(const WebWheelEvent& wheelEvent, WheelEventHandlingResult result) +void RemoteScrollingCoordinatorProxy::continueWheelEventHandling(Ref&& wheelEvent, WheelEventHandlingResult result) { bool willStartSwipe = m_scrollingTree->willWheelEventStartSwipeGesture(platform(wheelEvent)); - protect(webPageProxy())->continueWheelEventHandling(wheelEvent, result, willStartSwipe); + protect(webPageProxy())->continueWheelEventHandling(WTF::move(wheelEvent), result, willStartSwipe); } TrackingType RemoteScrollingCoordinatorProxy::eventTrackingTypeForPoint(WebCore::EventTrackingRegions::EventType eventType, IntPoint p) const diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h index e8bfb5d3ba3e..9a14ccabf840 100644 --- a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h +++ b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h @@ -106,8 +106,8 @@ class RemoteScrollingCoordinatorProxy : public CanMakeWeakPtr horizontal, std::optional vertical); virtual void cacheWheelEventScrollingAccelerationCurve(const NativeWebWheelEvent&) { } - virtual void handleWheelEvent(const WebWheelEvent&, WebCore::RectEdges rubberBandableEdges); - void continueWheelEventHandling(const WebWheelEvent&, WebCore::WheelEventHandlingResult); + virtual void handleWheelEvent(Ref&&, WebCore::RectEdges rubberBandableEdges); + void continueWheelEventHandling(Ref&&, WebCore::WheelEventHandlingResult); virtual void wheelEventHandlingCompleted(const WebCore::PlatformWheelEvent&, std::optional, std::optional, bool /* wasHandled */) { } virtual WebCore::PlatformWheelEvent filteredWheelEvent(const WebCore::PlatformWheelEvent& wheelEvent) { return wheelEvent; } diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.h b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.h index 94de342f498c..f10d4f75b952 100644 --- a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.h +++ b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.h @@ -93,7 +93,7 @@ class RemoteLayerTreeEventDispatcher void cacheWheelEventScrollingAccelerationCurve(const NativeWebWheelEvent&); - void handleWheelEvent(const WebWheelEvent&, WebCore::RectEdges rubberBandableEdges); + void handleWheelEvent(Ref&&, WebCore::RectEdges rubberBandableEdges); void wheelEventHandlingCompleted(const WebCore::PlatformWheelEvent&, std::optional, std::optional, bool wasHandled); void setScrollingTree(RefPtr&&); @@ -129,7 +129,7 @@ class RemoteLayerTreeEventDispatcher void wheelEventHysteresisUpdated(PAL::HysteresisState); - void willHandleWheelEvent(const WebWheelEvent&); + void willHandleWheelEvent(Ref&&); void continueWheelEventHandling(WebCore::WheelEventHandlingResult); void wheelEventWasHandledByScrollingThread(WebCore::WheelEventHandlingResult); @@ -181,7 +181,7 @@ class RemoteLayerTreeEventDispatcher Lock m_scrollingTreeLock; RefPtr m_scrollingTree WTF_GUARDED_BY_LOCK(m_scrollingTreeLock); - Deque m_wheelEventsBeingProcessed; // FIXME: Remove + Deque, 2> m_wheelEventsBeingProcessed; // FIXME: Remove const WeakPtr m_scrollingCoordinator; WebCore::PageIdentifier m_pageIdentifier; diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.mm b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.mm index a9a47356deb9..df8cee75067a 100644 --- a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.mm +++ b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteLayerTreeEventDispatcher.mm @@ -219,30 +219,30 @@ void displayLinkFired(PlatformDisplayID displayID, DisplayUpdate, bool wantsFull #endif } -void RemoteLayerTreeEventDispatcher::willHandleWheelEvent(const WebWheelEvent& wheelEvent) +void RemoteLayerTreeEventDispatcher::willHandleWheelEvent(Ref&& wheelEvent) { ASSERT(isMainRunLoop()); m_wheelEventActivityHysteresis.impulse(); - m_wheelEventsBeingProcessed.append(wheelEvent); + m_wheelEventsBeingProcessed.append(WTF::move(wheelEvent)); } -void RemoteLayerTreeEventDispatcher::handleWheelEvent(const WebWheelEvent& wheelEvent, RectEdges rubberBandableEdges) +void RemoteLayerTreeEventDispatcher::handleWheelEvent(Ref&& wheelEvent, RectEdges rubberBandableEdges) { ASSERT(isMainRunLoop()); auto scrollingTree = this->scrollingTree(); if (scrollingTree && scrollingTree->scrollingPerformanceTestingEnabled()) { - if (wheelEvent.phase() == WebWheelEvent::Phase::Began) + if (wheelEvent->phase() == WebWheelEvent::Phase::Began) startFingerDownSignpostInterval(); - if (wheelEvent.phase() == WebWheelEvent::Phase::Ended) + if (wheelEvent->phase() == WebWheelEvent::Phase::Ended) endFingerDownSignpostInterval(); } - willHandleWheelEvent(wheelEvent); + willHandleWheelEvent(wheelEvent.copyRef()); - ScrollingThread::dispatch([dispatcher = Ref { *this }, wheelEvent, rubberBandableEdges] { + ScrollingThread::dispatch([dispatcher = Ref { *this }, wheelEvent = WTF::move(wheelEvent), rubberBandableEdges] { dispatcher->scrollingThreadHandleWheelEvent(wheelEvent, rubberBandableEdges); }); } @@ -299,8 +299,8 @@ void displayLinkFired(PlatformDisplayID displayID, DisplayUpdate, bool wantsFull LOG_WITH_STREAM(Scrolling, stream << "RemoteLayerTreeEventDispatcher::continueWheelEventHandling - result " << handlingResult); - auto event = m_wheelEventsBeingProcessed.takeFirst(); - scrollingCoordinator->continueWheelEventHandling(event, handlingResult); + Ref event = m_wheelEventsBeingProcessed.takeFirst(); + scrollingCoordinator->continueWheelEventHandling(WTF::move(event), handlingResult); } OptionSet RemoteLayerTreeEventDispatcher::determineWheelEventProcessing(const PlatformWheelEvent& wheelEvent, RectEdges rubberBandableEdges) diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.h b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.h index c9c78f6b672e..7f5a73c35c61 100644 --- a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.h +++ b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.h @@ -45,7 +45,7 @@ class RemoteScrollingCoordinatorProxyMac final : public RemoteScrollingCoordinat private: void cacheWheelEventScrollingAccelerationCurve(const NativeWebWheelEvent&) override; - void handleWheelEvent(const WebWheelEvent&, WebCore::RectEdges rubberBandableEdges) override; + void handleWheelEvent(Ref&&, WebCore::RectEdges rubberBandableEdges) override; void wheelEventHandlingCompleted(const WebCore::PlatformWheelEvent&, std::optional, std::optional, bool wasHandled) override; WebCore::RequestsScrollHandling scrollingTreeNodeRequestsScroll(WebCore::ScrollingNodeID, const WebCore::RequestedScrollData&) override; diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.mm b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.mm index b1ddc9d37bc3..c01b2ec29064 100644 --- a/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.mm +++ b/Source/WebKit/UIProcess/RemoteLayerTree/mac/RemoteScrollingCoordinatorProxyMac.mm @@ -70,9 +70,9 @@ m_eventDispatcher->cacheWheelEventScrollingAccelerationCurve(nativeWheelEvent); } -void RemoteScrollingCoordinatorProxyMac::handleWheelEvent(const WebWheelEvent& wheelEvent, RectEdges rubberBandableEdges) +void RemoteScrollingCoordinatorProxyMac::handleWheelEvent(Ref&& wheelEvent, RectEdges rubberBandableEdges) { - m_eventDispatcher->handleWheelEvent(wheelEvent, rubberBandableEdges); + m_eventDispatcher->handleWheelEvent(WTF::move(wheelEvent), rubberBandableEdges); } void RemoteScrollingCoordinatorProxyMac::wheelEventHandlingCompleted(const PlatformWheelEvent& wheelEvent, std::optional scrollingNodeID, std::optional gestureState, bool wasHandled) diff --git a/Source/WebKit/UIProcess/ViewGestureController.h b/Source/WebKit/UIProcess/ViewGestureController.h index 3b9d52acb304..bb76223ba413 100644 --- a/Source/WebKit/UIProcess/ViewGestureController.h +++ b/Source/WebKit/UIProcess/ViewGestureController.h @@ -83,7 +83,9 @@ class Navigation; } #if PLATFORM(MAC) -typedef WebKit::NativeWebWheelEvent PlatformScrollEvent; +// A reference rather than a value: events are refcounted now, and this is only ever passed through, +// never stored. +typedef const WebKit::NativeWebWheelEvent& PlatformScrollEvent; #elif PLATFORM(GTK) typedef struct { WebCore::FloatSize delta; diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp index 3a280dd1dea0..9633d916694a 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp @@ -3913,14 +3913,14 @@ IntSize WebPageProxy::viewSize() const return pageClient ? pageClient->viewSize() : IntSize { }; } -void WebPageProxy::setInitialFocus(bool forward, bool isKeyboardEventValid, const std::optional& keyboardEvent, CompletionHandler&& callbackFunction) +void WebPageProxy::setInitialFocus(bool forward, bool isKeyboardEventValid, RefPtr&& keyboardEvent, CompletionHandler&& callbackFunction) { if (!hasRunningProcess()) { callbackFunction(); return; } - sendWithAsyncReply(Messages::WebPage::SetInitialFocus(forward, isKeyboardEventValid, keyboardEvent), [callbackFunction = WTF::move(callbackFunction), backgroundActivity = protect(m_legacyMainFrameProcess->throttler())->backgroundActivity("WebPageProxy::setInitialFocus"_s)] () mutable { + sendWithAsyncReply(Messages::WebPage::SetInitialFocus(forward, isKeyboardEventValid, WTF::move(keyboardEvent)), [callbackFunction = WTF::move(callbackFunction), backgroundActivity = protect(m_legacyMainFrameProcess->throttler())->backgroundActivity("WebPageProxy::setInitialFocus"_s)] () mutable { callbackFunction(); }); } @@ -4575,10 +4575,10 @@ void WebPageProxy::stageModeSessionDidEnd(std::optional nodeID) template requires std::derived_from -static std::optional removeOldRedundantEvent(Deque& queue, WebEventType incomingEventType, OptionSet eventFilter) +static RefPtr removeOldRedundantEvent(Deque>& queue, WebEventType incomingEventType, OptionSet eventFilter) { if (!eventFilter.contains(incomingEventType)) - return std::nullopt; + return nullptr; auto it = queue.rbegin(); auto end = queue.rend(); @@ -4588,24 +4588,25 @@ static std::optional removeOldRedundantEvent(Deque& queue, WebEventType in --end; for (; it != end; ++it) { - auto type = it->type(); + auto type = (*it)->type(); if (type == incomingEventType) { - auto event = *it; + RefPtr event = it->ptr(); queue.remove(--it.base()); return event; } if (!eventFilter.contains(type)) break; } - return std::nullopt; + return nullptr; } -void WebPageProxy::sendMouseEvent(FrameIdentifier frameID, const NativeWebMouseEvent& event, std::optional>&& sandboxExtensions) +void WebPageProxy::sendMouseEvent(FrameIdentifier frameID, Ref&& event, std::optional>&& sandboxExtensions) { - if (event.type() == WebEventType::MouseDown || event.type() == WebEventType::MouseUp) - processContainingFrame(frameID)->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event.authorizationToken()); + if (event->type() == WebEventType::MouseDown || event->type() == WebEventType::MouseUp) + processContainingFrame(frameID)->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event->authorizationToken()); - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::WebPage::MouseEvent(frameID, event, WTF::move(sandboxExtensions)), [weakThis = WeakPtr { *this }, eventType = event.type()] (IPC::Connection* connection, bool handled, std::optional remoteUserInputEventData) mutable { + auto eventType = event->type(); + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::WebPage::MouseEvent(frameID, WTF::move(event), WTF::move(sandboxExtensions)), [weakThis = WeakPtr { *this }, eventType] (IPC::Connection* connection, bool handled, std::optional remoteUserInputEventData) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis || !connection) return; @@ -4628,9 +4629,9 @@ void WebPageProxy::recordUIProcessUserActivation(const WebEvent& event) internals().lastActivationTimestamp = MonotonicTime::now(); } -void WebPageProxy::handleMouseEvent(const NativeWebMouseEvent& event) +void WebPageProxy::handleMouseEvent(Ref&& event) { - if (event.type() == WebEventType::MouseDown) + if (event->type() == WebEventType::MouseDown) launchInitialProcessIfNecessary(); if (!hasRunningProcess()) @@ -4642,11 +4643,11 @@ void WebPageProxy::handleMouseEvent(const NativeWebMouseEvent& event) recordUIProcessUserActivation(event); #if PLATFORM(GTK) || PLATFORM(WPE) - WTFBeginSignpost(event.signpostIdentifier(), HandleMouseEvent, "id: %" PRIuPTR ", type: %s", event.signpostIdentifier(), toString(event.type()).characters()); + WTFBeginSignpost(event->signpostIdentifier(), HandleMouseEvent, "id: %" PRIuPTR ", type: %s", event->signpostIdentifier(), toString(event->type()).characters()); #endif #if ENABLE(CONTEXT_MENU_EVENT) - if (event.button() == WebMouseEventButton::Right && event.type() == WebEventType::MouseDown) { + if (event->button() == WebMouseEventButton::Right && event->type() == WebEventType::MouseDown) { ASSERT(m_contextMenuPreventionState != EventPreventionState::Waiting); m_contextMenuPreventionState = EventPreventionState::Waiting; } @@ -4655,15 +4656,16 @@ void WebPageProxy::handleMouseEvent(const NativeWebMouseEvent& event) // If we receive multiple mousemove or mouseforcechanged events and the most recent mousemove or mouseforcechanged event // (respectively) has not yet been sent to WebProcess for processing, remove the pending mouse event and insert the new // event in the queue. - auto removedEvent = removeOldRedundantEvent(internals().mouseEventQueue, event.type(), { WebEventType::MouseMove, WebEventType::MouseForceChanged }); + auto removedEvent = removeOldRedundantEvent(internals().mouseEventQueue, event->type(), { WebEventType::MouseMove, WebEventType::MouseForceChanged }); if (removedEvent && removedEvent->type() == WebEventType::MouseMove) - internals().coalescedMouseEvents.append(CheckedRef { *removedEvent }.get()); + internals().coalescedMouseEvents.append(removedEvent.releaseNonNull()); - internals().mouseEventQueue.append(event); + auto eventType = event->type(); + internals().mouseEventQueue.append(WTF::move(event)); - LOG_WITH_STREAM(MouseHandling, stream << "UIProcess: " << (removedEvent ? "replaced" : "enqueued") << " mouse event " << event.type() << " (queue size " << internals().mouseEventQueue.size() << ", coalesced events size " << internals().coalescedMouseEvents.size() << ")"); + LOG_WITH_STREAM(MouseHandling, stream << "UIProcess: " << (removedEvent ? "replaced" : "enqueued") << " mouse event " << eventType << " (queue size " << internals().mouseEventQueue.size() << ", coalesced events size " << internals().coalescedMouseEvents.size() << ")"); - if (event.type() != WebEventType::MouseMove) + if (eventType != WebEventType::MouseMove) send(Messages::WebPage::FlushDeferredDidReceiveMouseEvent()); if (internals().mouseEventQueue.size() == 1) // Otherwise, called from DidReceiveEvent message handler. @@ -4672,9 +4674,9 @@ void WebPageProxy::handleMouseEvent(const NativeWebMouseEvent& event) WEBPAGEPROXY_RELEASE_LOG(MouseHandling, "handleMouseEvent: skipped called processNextQueuedMouseEvent 20 times, possibly stuck?"); } -void WebPageProxy::dispatchMouseDidMoveOverElementAsynchronously(const NativeWebMouseEvent& event) +void WebPageProxy::dispatchMouseDidMoveOverElementAsynchronously(Ref&& event) { - sendWithAsyncReply(Messages::WebPage::PerformHitTestForMouseEvent { event }, [this, protectedThis = Ref { *this }] (WebHitTestResultData&& hitTestResult, OptionSet modifiers) { + sendWithAsyncReply(Messages::WebPage::PerformHitTestForMouseEvent { WTF::move(event) }, [this, protectedThis = Ref { *this }] (WebHitTestResultData&& hitTestResult, OptionSet modifiers) { if (!isClosed()) mouseDidMoveOverElement(WTF::move(hitTestResult), modifiers); }); @@ -4702,7 +4704,7 @@ void WebPageProxy::processNextQueuedMouseEvent() ASSERT(!internals().mouseEventQueue.isEmpty()); m_deferredMouseEvents = 0; - const CheckedRef event = internals().mouseEventQueue.first(); + const Ref event = internals().mouseEventQueue.first(); #if ENABLE(CONTEXT_MENUS) if (m_waitingForContextMenuToShow) { @@ -4729,16 +4731,16 @@ void WebPageProxy::processNextQueuedMouseEvent() sandboxExtensions = SandboxExtension::createHandlesForMachLookup({ "com.apple.iconservices"_s, "com.apple.iconservices.store"_s }, process->auditToken(), SandboxExtension::MachBootstrapOptions::EnableMachBootstrap); #endif - auto eventWithCoalescedEvents = event; - if (event->type() == WebEventType::MouseMove) { - internals().coalescedMouseEvents.append(event); - eventWithCoalescedEvents->setCoalescedEvents(internals().coalescedMouseEvents); + // A copy, not the event itself: the event is about to own this vector, and a Ref to itself + // would be a reference cycle. + internals().coalescedMouseEvents.append(event->copy()); + event->setCoalescedEvents(internals().coalescedMouseEvents); } LOG_WITH_STREAM(MouseHandling, stream << "UIProcess: sent mouse event " << eventType << " (queue size " << internals().mouseEventQueue.size() << ", coalesced events size " << internals().coalescedMouseEvents.size() << ")"); - sendMouseEvent(targetFrame->frameID(), eventWithCoalescedEvents, WTF::move(sandboxExtensions)); + sendMouseEvent(targetFrame->frameID(), event.copyRef(), WTF::move(sandboxExtensions)); internals().coalescedMouseEvents.clear(); } @@ -4755,7 +4757,7 @@ void WebPageProxy::processNextQueuedGestureEvent() ASSERT(!internals().gestureEventQueue.isEmpty()); m_deferredGestureEvents = 0; - const CheckedRef event = internals().gestureEventQueue.first(); + const Ref event = internals().gestureEventQueue.first(); const auto eventType = event->type(); RefPtr targetFrame = m_mainFrame; @@ -4763,7 +4765,7 @@ void WebPageProxy::processNextQueuedGestureEvent() LOG_WITH_STREAM(GestureHandling, stream << "UIProcess: sent gesture event " << eventType << " (queue size " << internals().gestureEventQueue.size() << ", dropped gestures since last gesture event processed: " << internals().droppedGestureEventCount << ")"); - sendGestureEvent(targetFrame->frameID(), event); + sendGestureEvent(targetFrame->frameID(), event.copyRef()); internals().droppedGestureEventCount = 0; } @@ -4861,18 +4863,19 @@ void WebPageProxy::flushPendingTouchEventCallbacks() #endif #if PLATFORM(IOS_FAMILY) -void WebPageProxy::handleWheelEventWithoutScrolling(const WebWheelEvent& event, CompletionHandler&& completionHandler) +void WebPageProxy::handleWheelEventWithoutScrolling(Ref&& event, CompletionHandler&& completionHandler) { if (!m_mainFrame) { completionHandler(false); return; } - sendWheelEventWithoutScrolling(m_mainFrame->frameID(), event, WTF::move(completionHandler)); + sendWheelEventWithoutScrolling(m_mainFrame->frameID(), WTF::move(event), WTF::move(completionHandler)); } -void WebPageProxy::sendWheelEventWithoutScrolling(WebCore::FrameIdentifier frameID, const WebWheelEvent& event, CompletionHandler&& completionHandler) +void WebPageProxy::sendWheelEventWithoutScrolling(WebCore::FrameIdentifier frameID, Ref&& event, CompletionHandler&& completionHandler) { - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::WebPage::DispatchWheelEventWithoutScrolling(frameID, event), [weakThis = WeakPtr { *this }, event, completionHandler = WTF::move(completionHandler)](bool defaultPrevented, std::optional remoteWheelEventData) mutable { + // The lambda captures an independent copy, since the retry path below mutates its position. + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::WebPage::DispatchWheelEventWithoutScrolling(frameID, event.copyRef()), [weakThis = WeakPtr { *this }, event = event->copy(), completionHandler = WTF::move(completionHandler)](bool defaultPrevented, std::optional remoteWheelEventData) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis) { completionHandler(false); @@ -4880,9 +4883,8 @@ void WebPageProxy::sendWheelEventWithoutScrolling(WebCore::FrameIdentifier frame } if (remoteWheelEventData) { - auto transformedEvent = event; - transformedEvent.setPosition(roundedIntPoint(remoteWheelEventData->transformedPoint)); - protectedThis->sendWheelEventWithoutScrolling(remoteWheelEventData->targetFrameID, transformedEvent, WTF::move(completionHandler)); + event->setPosition(roundedIntPoint(remoteWheelEventData->transformedPoint)); + protectedThis->sendWheelEventWithoutScrolling(remoteWheelEventData->targetFrameID, WTF::move(event), WTF::move(completionHandler)); return; } @@ -4891,7 +4893,7 @@ void WebPageProxy::sendWheelEventWithoutScrolling(WebCore::FrameIdentifier frame } #endif -void WebPageProxy::handleNativeWheelEvent(const NativeWebWheelEvent& nativeWheelEvent) +void WebPageProxy::handleNativeWheelEvent(Ref&& nativeWheelEvent) { if (!hasRunningProcess()) return; @@ -4900,11 +4902,11 @@ void WebPageProxy::handleNativeWheelEvent(const NativeWebWheelEvent& nativeWheel cacheWheelEventScrollingAccelerationCurve(nativeWheelEvent); - if (!wheelEventCoalescer().shouldDispatchEvent(nativeWheelEvent)) + if (!wheelEventCoalescer().shouldDispatchEvent(WTF::move(nativeWheelEvent))) return; - auto eventToDispatch = *wheelEventCoalescer().nextEventToDispatch(); - handleWheelEvent(eventToDispatch); + RefPtr eventToDispatch = wheelEventCoalescer().nextEventToDispatch(); + handleWheelEvent(eventToDispatch.releaseNonNull()); } static RectEdges resolvedRubberBandingBehaviorEdges(RectEdges rubberBandableEdges, bool alwaysBounceVertical, bool alwaysBounceHorizontal) @@ -4932,13 +4934,13 @@ static RectEdges resolvedRubberBandingBehaviorEd return result; } -void WebPageProxy::handleWheelEvent(const WebWheelEvent& wheelEvent) +void WebPageProxy::handleWheelEvent(Ref&& wheelEvent) { if (!hasRunningProcess()) return; if (protect(drawingArea())->shouldSendWheelEventsToEventDispatcher()) { - continueWheelEventHandling(wheelEvent, { WheelEventProcessingSteps::SynchronousScrolling, false }, { }); + continueWheelEventHandling(WTF::move(wheelEvent), { WheelEventProcessingSteps::SynchronousScrolling, false }, { }); return; } @@ -4947,20 +4949,20 @@ void WebPageProxy::handleWheelEvent(const WebWheelEvent& wheelEvent) auto rubberBandableEdges = rubberBandableEdgesRespectingHistorySwipe(); auto rubberBandingBehavior = resolvedRubberBandingBehaviorEdges(rubberBandableEdges, alwaysBounceVertical(), alwaysBounceHorizontal()); - scrollingCoordinatorProxy->handleWheelEvent(wheelEvent, rubberBandingBehavior); + scrollingCoordinatorProxy->handleWheelEvent(WTF::move(wheelEvent), rubberBandingBehavior); // continueWheelEventHandling() will get called after the event has been handled by the scrolling thread. } #endif } -void WebPageProxy::continueWheelEventHandling(const WebWheelEvent& wheelEvent, const WheelEventHandlingResult& result, std::optional willStartSwipe) +void WebPageProxy::continueWheelEventHandling(Ref&& wheelEvent, const WheelEventHandlingResult& result, std::optional willStartSwipe) { LOG_WITH_STREAM(WheelEvents, stream << "WebPageProxy::continueWheelEventHandling - " << result); if (!result.needsMainThreadProcessing()) { - if (m_mainFrame && wheelEvent.phase() == WebWheelEvent::Phase::Began) { + if (m_mainFrame && wheelEvent->phase() == WebWheelEvent::Phase::Began) { // When wheel events are handled entirely in the UI process, we still need to tell the web process where the mouse is for cursor updates. - sendToProcessContainingFrame(m_mainFrame->frameID(), Messages::WebPage::SetLastKnownMousePosition(m_mainFrame->frameID(), wheelEvent.position(), wheelEvent.globalPosition(), WebCore::LastKnownMousePositionSource::Wheel)); + sendToProcessContainingFrame(m_mainFrame->frameID(), Messages::WebPage::SetLastKnownMousePosition(m_mainFrame->frameID(), wheelEvent->position(), wheelEvent->globalPosition(), WebCore::LastKnownMousePositionSource::Wheel)); } wheelEventHandlingCompleted(result.wasHandled); @@ -4973,10 +4975,10 @@ void WebPageProxy::continueWheelEventHandling(const WebWheelEvent& wheelEvent, c auto rubberBandableEdges = rubberBandableEdgesRespectingHistorySwipe(); auto rubberBandingBehavior = resolvedRubberBandingBehaviorEdges(rubberBandableEdges, alwaysBounceVertical(), alwaysBounceHorizontal()); - sendWheelEvent(m_mainFrame->frameID(), wheelEvent, result.steps, rubberBandingBehavior, willStartSwipe, result.wasHandled); + sendWheelEvent(m_mainFrame->frameID(), WTF::move(wheelEvent), result.steps, rubberBandingBehavior, willStartSwipe, result.wasHandled); } -void WebPageProxy::sendWheelEvent(WebCore::FrameIdentifier frameID, const WebWheelEvent& event, OptionSet processingSteps, RectEdges rubberBandableEdges, std::optional willStartSwipe, bool wasHandledForScrolling) +void WebPageProxy::sendWheelEvent(WebCore::FrameIdentifier frameID, Ref&& event, OptionSet processingSteps, RectEdges rubberBandableEdges, std::optional willStartSwipe, bool wasHandledForScrolling) { #if HAVE(DISPLAY_LINK) internals().wheelEventActivityHysteresis.impulse(); @@ -4985,14 +4987,14 @@ void WebPageProxy::sendWheelEvent(WebCore::FrameIdentifier frameID, const WebWhe Ref process = processContainingFrame(frameID); if (protect(drawingArea())->shouldSendWheelEventsToEventDispatcher()) { sendWheelEventScrollingAccelerationCurveIfNecessary(frameID, event); - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::WheelEvent(webPageIDInProcess(process), event, rubberBandableEdges), [weakThis = WeakPtr { *this }] (IPC::Connection* connection, bool handled) mutable { + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::WheelEvent(webPageIDInProcess(process), WTF::move(event), rubberBandableEdges), [weakThis = WeakPtr { *this }] (IPC::Connection* connection, bool handled) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis || !connection) return; protectedThis->wheelEventHandlingCompleted(handled); }); } else { - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::WebPage::HandleWheelEvent(frameID, event, processingSteps, willStartSwipe), [weakThis = WeakPtr { *this }, wheelEvent = event, processingSteps, rubberBandableEdges, willStartSwipe, wasHandledForScrolling] (IPC::Connection* connection, std::optional nodeID, std::optional gestureState, bool handled, std::optional remoteWheelEventData) mutable { + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::WebPage::HandleWheelEvent(frameID, event.copyRef(), processingSteps, willStartSwipe), [weakThis = WeakPtr { *this }, wheelEvent = event->copy(), processingSteps, rubberBandableEdges, willStartSwipe, wasHandledForScrolling] (IPC::Connection* connection, std::optional nodeID, std::optional gestureState, bool handled, std::optional remoteWheelEventData) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis) return; @@ -5003,8 +5005,8 @@ void WebPageProxy::sendWheelEvent(WebCore::FrameIdentifier frameID, const WebWhe MESSAGE_CHECK_BASE(!remoteWheelEventData || protect(protectedThis->preferences())->siteIsolationEnabled(), connection); if (remoteWheelEventData) { - wheelEvent.setPosition(roundedIntPoint(remoteWheelEventData->transformedPoint)); - protectedThis->sendWheelEvent(remoteWheelEventData->targetFrameID, wheelEvent, processingSteps, rubberBandableEdges, willStartSwipe, wasHandledForScrolling); + wheelEvent->setPosition(roundedIntPoint(remoteWheelEventData->transformedPoint)); + protectedThis->sendWheelEvent(remoteWheelEventData->targetFrameID, wheelEvent.copyRef(), processingSteps, rubberBandableEdges, willStartSwipe, wasHandledForScrolling); return; } @@ -5046,14 +5048,14 @@ void WebPageProxy::wheelEventHandlingCompleted(bool wasHandled) LOG_WITH_STREAM(WheelEvents, stream << "WebPageProxy::wheelEventHandlingCompleted - no event, handled " << wasHandled); if (oldestProcessedEvent && !wasHandled) { - CheckedRef event = *oldestProcessedEvent; + Ref event = *oldestProcessedEvent; m_uiClient->didNotHandleWheelEvent(this, event.get()); if (RefPtr pageClient = m_pageClient.get()) pageClient->wheelEventWasNotHandledByWebCore(event.get()); } - if (auto eventToSend = wheelEventCoalescer().nextEventToDispatch()) { - handleWheelEvent(CheckedRef { *eventToSend }.get()); + if (RefPtr eventToSend = wheelEventCoalescer().nextEventToDispatch()) { + handleWheelEvent(eventToSend.releaseNonNull()); return; } @@ -5154,14 +5156,14 @@ const NativeWebKeyboardEvent& WebPageProxy::firstQueuedKeyEvent() const return internals().keyEventQueue.first(); } -void WebPageProxy::sendKeyEvent(const NativeWebKeyboardEvent& event) +void WebPageProxy::sendKeyEvent(Ref&& event) { RefPtr targetFrame = m_focusedFrame ? m_focusedFrame : m_mainFrame; auto targetFrameID = targetFrame->frameID(); Ref targetProcess = targetFrame->process(); - targetProcess->startResponsivenessTimer(event.type() == WebEventType::KeyDown ? WebProcessProxy::UseLazyStop::Yes : WebProcessProxy::UseLazyStop::No); - targetProcess->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event.authorizationToken()); - sendWithAsyncReplyToProcessContainingFrame(targetFrameID, Messages::WebPage::KeyEvent(targetFrameID, event), [weakThis = WeakPtr { *this }] (IPC::Connection* connection, bool handled) mutable { + targetProcess->startResponsivenessTimer(event->type() == WebEventType::KeyDown ? WebProcessProxy::UseLazyStop::Yes : WebProcessProxy::UseLazyStop::No); + targetProcess->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event->authorizationToken()); + sendWithAsyncReplyToProcessContainingFrame(targetFrameID, Messages::WebPage::KeyEvent(targetFrameID, WTF::move(event)), [weakThis = WeakPtr { *this }] (IPC::Connection* connection, bool handled) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis || !connection) return; @@ -5174,7 +5176,7 @@ void WebPageProxy::sendKeyEvent(const NativeWebKeyboardEvent& event) }); } -bool WebPageProxy::handleKeyboardEvent(const NativeWebKeyboardEvent& event) +bool WebPageProxy::handleKeyboardEvent(Ref&& event) { if (!hasRunningProcess()) return false; @@ -5186,14 +5188,14 @@ bool WebPageProxy::handleKeyboardEvent(const NativeWebKeyboardEvent& event) recordUIProcessUserActivation(event); - LOG_WITH_STREAM(KeyHandling, stream << "WebPageProxy::handleKeyboardEvent: " << event.type()); + LOG_WITH_STREAM(KeyHandling, stream << "WebPageProxy::handleKeyboardEvent: " << event->type()); - internals().keyEventQueue.append(event); + internals().keyEventQueue.append(event.copyRef()); // Otherwise, sent from DidReceiveEvent message handler. if (internals().keyEventQueue.size() == 1) { LOG(KeyHandling, " UI process: sent keyEvent from handleKeyboardEvent"); - sendKeyEvent(event); + sendKeyEvent(WTF::move(event)); } return true; @@ -5306,9 +5308,9 @@ TrackingType WebPageProxy::touchEventTrackingType(const WebTouchEvent& touchStar #endif #if ENABLE(MAC_GESTURE_EVENTS) -void WebPageProxy::sendGestureEvent(FrameIdentifier frameID, const NativeWebGestureEvent& event) +void WebPageProxy::sendGestureEvent(FrameIdentifier frameID, Ref&& event) { - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::GestureEvent(frameID, webPageIDInProcess(processContainingFrame(frameID)), event), [protectedThis = Ref { *this }](IPC::Connection* connection, std::optional&& eventType, bool handled, std::optional&& remoteUserInputEventData) { + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::GestureEvent(frameID, webPageIDInProcess(processContainingFrame(frameID)), WTF::move(event)), [protectedThis = Ref { *this }](IPC::Connection* connection, std::optional&& eventType, bool handled, std::optional&& remoteUserInputEventData) { if (!protectedThis->m_pageClient) return; if (!eventType) @@ -5325,7 +5327,7 @@ void WebPageProxy::sendGestureEvent(FrameIdentifier frameID, const NativeWebGest }); } -void WebPageProxy::handleGestureEvent(const NativeWebGestureEvent& event) +void WebPageProxy::handleGestureEvent(Ref&& event) { if (!hasRunningProcess()) return; @@ -5333,10 +5335,10 @@ void WebPageProxy::handleGestureEvent(const NativeWebGestureEvent& event) if (!m_mainFrame) return; - if (removeOldRedundantEvent(internals().gestureEventQueue, event.type(), { WebEventType::GestureChange })) + if (removeOldRedundantEvent(internals().gestureEventQueue, event->type(), { WebEventType::GestureChange })) internals().droppedGestureEventCount++; - internals().gestureEventQueue.append(event); + internals().gestureEventQueue.append(WTF::move(event)); if (internals().gestureEventQueue.size() == 1) // Otherwise, called from DidReceiveEvent message handler. processNextQueuedGestureEvent(); @@ -5346,33 +5348,33 @@ void WebPageProxy::handleGestureEvent(const NativeWebGestureEvent& event) #endif #if ENABLE(IOS_TOUCH_EVENTS) -void WebPageProxy::sendPreventableTouchEvent(WebCore::FrameIdentifier frameID, const WebTouchEvent& event) +void WebPageProxy::sendPreventableTouchEvent(WebCore::FrameIdentifier frameID, Ref&& event) { - if (event.type() == WebEventType::TouchEnd && protect(preferences())->verifyWindowOpenUserGestureFromUIProcess()) - processContainingFrame(frameID)->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event.authorizationToken()); + if (event->type() == WebEventType::TouchEnd && protect(preferences())->verifyWindowOpenUserGestureFromUIProcess()) + processContainingFrame(frameID)->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event->authorizationToken()); - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::TouchEvent(webPageIDInProcess(processContainingFrame(frameID)), frameID, event), [this, weakThis = WeakPtr { *this }, event] (IPC::Connection* connection, bool handled, std::optional remoteWebTouchEvent) mutable { + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::TouchEvent(webPageIDInProcess(processContainingFrame(frameID)), frameID, event.copyRef()), [this, weakThis = WeakPtr { *this }, event = event.copyRef()] (IPC::Connection* connection, bool handled, std::optional remoteWebTouchEvent) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis) return; if (remoteWebTouchEvent) - return sendPreventableTouchEvent(remoteWebTouchEvent->targetFrameID, remoteWebTouchEvent->transformedEvent); + return sendPreventableTouchEvent(remoteWebTouchEvent->targetFrameID, WTF::move(remoteWebTouchEvent->transformedEvent)); bool didFinishDeferringTouchStart = false; - ASSERT_IMPLIES(event.type() == WebEventType::TouchStart, m_handlingPreventableTouchStartCount); - if (event.type() == WebEventType::TouchStart && m_handlingPreventableTouchStartCount) + ASSERT_IMPLIES(event->type() == WebEventType::TouchStart, m_handlingPreventableTouchStartCount); + if (event->type() == WebEventType::TouchStart && m_handlingPreventableTouchStartCount) didFinishDeferringTouchStart = !--m_handlingPreventableTouchStartCount; bool didFinishDeferringTouchMove = false; - if (event.type() == WebEventType::TouchMove && m_touchMovePreventionState == EventPreventionState::Waiting) { + if (event->type() == WebEventType::TouchMove && m_touchMovePreventionState == EventPreventionState::Waiting) { m_touchMovePreventionState = handled ? EventPreventionState::Prevented : EventPreventionState::Allowed; didFinishDeferringTouchMove = true; } bool didFinishDeferringTouchEnd = false; - ASSERT_IMPLIES(event.type() == WebEventType::TouchEnd, m_handlingPreventableTouchEndCount); - if (event.type() == WebEventType::TouchEnd && m_handlingPreventableTouchEndCount) + ASSERT_IMPLIES(event->type() == WebEventType::TouchEnd, m_handlingPreventableTouchEndCount); + if (event->type() == WebEventType::TouchEnd && m_handlingPreventableTouchEndCount) didFinishDeferringTouchEnd = !--m_handlingPreventableTouchEndCount; if (connection) @@ -5465,7 +5467,7 @@ void WebPageProxy::handlePreventableTouchEvent(NativeWebTouchEvent& event) if (isTouchEnd) ++m_handlingPreventableTouchEndCount; - sendPreventableTouchEvent(m_mainFrame->frameID(), event); + sendPreventableTouchEvent(m_mainFrame->frameID(), Ref { event }); } void WebPageProxy::didBeginTouchPoint(FloatPoint locationInRootView) @@ -5476,19 +5478,19 @@ void WebPageProxy::didBeginTouchPoint(FloatPoint locationInRootView) send(Messages::WebPage::DidBeginTouchPoint(locationInRootView)); } -void WebPageProxy::sendUnpreventableTouchEvent(WebCore::FrameIdentifier frameID, const WebTouchEvent& event) +void WebPageProxy::sendUnpreventableTouchEvent(WebCore::FrameIdentifier frameID, Ref&& event) { - if (event.type() == WebEventType::TouchEnd && protect(preferences())->verifyWindowOpenUserGestureFromUIProcess()) - processContainingFrame(frameID)->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event.authorizationToken()); + if (event->type() == WebEventType::TouchEnd && protect(preferences())->verifyWindowOpenUserGestureFromUIProcess()) + processContainingFrame(frameID)->recordUserGestureAuthorizationToken(webPageIDInMainFrameProcess(), event->authorizationToken()); - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::TouchEvent(webPageIDInProcess(processContainingFrame(frameID)), frameID, event), [protectedThis = Ref { *this }] (bool, std::optional remoteWebTouchEvent) mutable { + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::TouchEvent(webPageIDInProcess(processContainingFrame(frameID)), frameID, WTF::move(event)), [protectedThis = Ref { *this }] (bool, std::optional remoteWebTouchEvent) mutable { if (!remoteWebTouchEvent) return; - protectedThis->sendUnpreventableTouchEvent(remoteWebTouchEvent->targetFrameID, remoteWebTouchEvent->transformedEvent); + protectedThis->sendUnpreventableTouchEvent(remoteWebTouchEvent->targetFrameID, WTF::move(remoteWebTouchEvent->transformedEvent)); }); } -void WebPageProxy::handleUnpreventableTouchEvent(const NativeWebTouchEvent& event) +void WebPageProxy::handleUnpreventableTouchEvent(NativeWebTouchEvent& event) { if (!hasRunningProcess()) return; @@ -5502,7 +5504,7 @@ void WebPageProxy::handleUnpreventableTouchEvent(const NativeWebTouchEvent& even if (touchEventsTrackingType == TrackingType::NotTracking) return; - sendUnpreventableTouchEvent(m_mainFrame->frameID(), event); + sendUnpreventableTouchEvent(m_mainFrame->frameID(), Ref { event }); if (event.allTouchPointsAreReleased()) { internals().touchEventTracking.reset(); @@ -5523,7 +5525,7 @@ void WebPageProxy::processNextQueuedTouchEvent() auto& queuedEvent = internals().touchEventQueue.first(); protect(legacyMainFrameProcess())->startResponsivenessTimer(); - sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::TouchEvent(webPageIDInProcess(processContainingFrame(frameID)), frameID, queuedEvent.forwardedEvent), [this, protectedThis = Ref { *this }] (IPC::Connection* connection, WebKit::WebEventType eventType, bool handled) mutable { + sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::EventDispatcher::TouchEvent(webPageIDInProcess(processContainingFrame(frameID)), frameID, queuedEvent.forwardedEvent.copyRef()), [this, protectedThis = Ref { *this }] (IPC::Connection* connection, WebKit::WebEventType eventType, bool handled) mutable { RefPtr pageClient = this->pageClient(); if (!pageClient) return; @@ -5542,7 +5544,7 @@ void WebPageProxy::processNextQueuedTouchEvent() MESSAGE_CHECK_BASE(!internals().touchEventQueue.isEmpty(), connection); auto queuedEvents = internals().touchEventQueue.takeFirst(); - MESSAGE_CHECK_BASE(eventType == queuedEvents.forwardedEvent.type(), connection); + MESSAGE_CHECK_BASE(eventType == queuedEvents.forwardedEvent->type(), connection); protect(legacyMainFrameProcess())->stopResponsivenessTimer(); pageClient->doneWithTouchEvent(queuedEvents.forwardedEvent, handled); @@ -5556,7 +5558,7 @@ void WebPageProxy::processNextQueuedTouchEvent() }); } -void WebPageProxy::handleTouchEvent(IPC::Connection*, const NativeWebTouchEvent& event) +void WebPageProxy::handleTouchEvent(IPC::Connection*, Ref&& event) { if (!hasRunningProcess()) return; @@ -5564,15 +5566,15 @@ void WebPageProxy::handleTouchEvent(IPC::Connection*, const NativeWebTouchEvent& if (!m_mainFrame) return; - if (event.type() == WebEventType::TouchMove && !internals().touchEventQueue.isEmpty()) { + if (event->type() == WebEventType::TouchMove && !internals().touchEventQueue.isEmpty()) { QueuedTouchEvents& lastEvent = internals().touchEventQueue.last(); - if (lastEvent.forwardedEvent.type() == WebEventType::TouchMove) { - lastEvent.deferredTouchEvents.append(event); + if (lastEvent.forwardedEvent->type() == WebEventType::TouchMove) { + lastEvent.deferredTouchEvents.append(WTF::move(event)); return; } } - internals().touchEventQueue.append(event); + internals().touchEventQueue.append(WTF::move(event)); if (internals().touchEventQueue.size() == 1) processNextQueuedTouchEvent(); @@ -5583,7 +5585,7 @@ void WebPageProxy::touchEventHandlingCompleted(IPC::Connection* connection, std: MESSAGE_CHECK_BASE(!internals().touchEventQueue.isEmpty(), connection); auto queuedEvents = internals().touchEventQueue.takeFirst(); if (eventType) - MESSAGE_CHECK_BASE(*eventType == queuedEvents.forwardedEvent.type(), connection); + MESSAGE_CHECK_BASE(*eventType == queuedEvents.forwardedEvent->type(), connection); RefPtr pageClient = this->pageClient(); if (!pageClient) @@ -5601,7 +5603,7 @@ void WebPageProxy::touchEventHandlingCompleted(IPC::Connection* connection, std: #endif } -void WebPageProxy::handleTouchEvent(IPC::Connection* connection, const NativeWebTouchEvent& event) +void WebPageProxy::handleTouchEvent(IPC::Connection* connection, Ref&& event) { if (!hasRunningProcess()) return; @@ -5615,9 +5617,9 @@ void WebPageProxy::handleTouchEvent(IPC::Connection* connection, const NativeWeb // and animation on the page itself (kinetic scrolling, tap to zoom) etc, then // we do not send any of the events to the page even if is has listeners. if (!m_areActiveDOMObjectsAndAnimationsSuspended) { - internals().touchEventQueue.append(event); + internals().touchEventQueue.append(event.copyRef()); protect(legacyMainFrameProcess())->startResponsivenessTimer(); - sendWithAsyncReply(Messages::WebPage::TouchEvent(event), [this, protectedThis = Ref { *this }] (IPC::Connection* connection, std::optional eventType, bool handled) { + sendWithAsyncReply(Messages::WebPage::TouchEvent(event.copyRef()), [this, protectedThis = Ref { *this }] (IPC::Connection* connection, std::optional eventType, bool handled) { if (!m_pageClient) return; if (eventType && connection) @@ -5633,11 +5635,11 @@ void WebPageProxy::handleTouchEvent(IPC::Connection* connection, const NativeWeb // We attach the incoming events to the newest queued event so that all // the events are delivered in the correct order when the event is dequed. QueuedTouchEvents& lastEvent = internals().touchEventQueue.last(); - lastEvent.deferredTouchEvents.append(event); + lastEvent.deferredTouchEvents.append(event.copyRef()); } } - if (event.allTouchPointsAreReleased()) { + if (event->allTouchPointsAreReleased()) { internals().touchEventTracking.reset(); didReleaseAllTouchPoints(); } @@ -12777,11 +12779,10 @@ NativeWebMouseEvent* WebPageProxy::Internals::currentlyProcessedMouseDownEvent() if (mouseEventQueue.isEmpty()) return nullptr; - auto& event = mouseEventQueue.first(); - if (event.type() != WebEventType::MouseDown) + if (mouseEventQueue.first()->type() != WebEventType::MouseDown) return nullptr; - return &event; + return mouseEventQueue.first().ptr(); } #endif @@ -13589,14 +13590,14 @@ void WebPageProxy::setCursorHiddenUntilMouseMoves(bool hiddenUntilMouseMoves) void WebPageProxy::mouseEventHandlingCompleted(bool handled, std::optional remoteUserInputEventData) { if (remoteUserInputEventData) { - CheckedRef event = internals().mouseEventQueue.first(); + Ref event = internals().mouseEventQueue.first(); const auto originalPosition = event->position(); const auto transformedPosition = remoteUserInputEventData->transformedPoint; event->setPosition(transformedPosition); const auto offset = originalPosition - transformedPosition; auto coalescedEvents = event->coalescedEvents(); - for (CheckedRef coalescedEvent : coalescedEvents) { + for (Ref coalescedEvent : coalescedEvents) { const auto adjustedPosition = coalescedEvent->position() - offset; coalescedEvent->setPosition(adjustedPosition); } @@ -13605,16 +13606,16 @@ void WebPageProxy::mouseEventHandlingCompleted(bool handled, std::optionaltargetFrameID)) { startResponsivenessTimerForMouseEvent(*targetFrame, event->type()); - sendMouseEvent(remoteUserInputEventData->targetFrameID, event, { }); + sendMouseEvent(remoteUserInputEventData->targetFrameID, event.copyRef(), { }); } return; } // Retire the last sent event now that WebProcess is done handling it. - auto event = internals().mouseEventQueue.takeFirst(); + Ref event = internals().mouseEventQueue.takeFirst(); #if ENABLE(CONTEXT_MENU_EVENT) - if (event.button() == WebMouseEventButton::Right) { - if (event.type() == WebEventType::MouseDown) { + if (event->button() == WebMouseEventButton::Right) { + if (event->type() == WebEventType::MouseDown) { ASSERT(m_contextMenuPreventionState == EventPreventionState::Waiting); m_contextMenuPreventionState = handled ? EventPreventionState::Prevented : EventPreventionState::Allowed; } else if (m_contextMenuPreventionState != EventPreventionState::Waiting) @@ -13625,11 +13626,11 @@ void WebPageProxy::mouseEventHandlingCompleted(bool handled, std::optionalsignpostIdentifier(), HandleMouseEvent, "handled: %s", handled ? "yes" : "no"); + for (auto& coalescedEvent : event->coalescedEvents()) { + if (coalescedEvent->signpostIdentifier() == event->signpostIdentifier()) continue; - WTFEndSignpost(coalescedEvent.signpostIdentifier(), HandleMouseEvent, "coalesced with: %" PRIuPTR, event.signpostIdentifier()); + WTFEndSignpost(coalescedEvent->signpostIdentifier(), HandleMouseEvent, "coalesced with: %" PRIuPTR, event->signpostIdentifier()); } #endif @@ -13647,14 +13648,14 @@ void WebPageProxy::mouseEventHandlingCompleted(bool handled, std::optional eventType, bool handled, std::optional remoteUserInputEventData) { if (remoteUserInputEventData) { - sendGestureEvent(remoteUserInputEventData->targetFrameID, internals().gestureEventQueue.first()); + sendGestureEvent(remoteUserInputEventData->targetFrameID, internals().gestureEventQueue.first().copyRef()); return; } // Retire the last sent event now that WebProcess is done handling it. - auto event = internals().gestureEventQueue.takeFirst(); + Ref event = internals().gestureEventQueue.takeFirst(); if (eventType) - MESSAGE_CHECK(m_legacyMainFrameProcess, *eventType == event.type()); + MESSAGE_CHECK(m_legacyMainFrameProcess, *eventType == event->type()); if (RefPtr pageClient = this->pageClient(); !handled && pageClient) pageClient->gestureEventWasNotHandledByWebCore(event); @@ -13666,18 +13667,18 @@ void WebPageProxy::gestureEventHandlingCompleted(std::optional eve void WebPageProxy::keyEventHandlingCompleted(bool handled) { - auto event = internals().keyEventQueue.takeFirst(); + Ref event = internals().keyEventQueue.takeFirst(); #if PLATFORM(WIN) - if (!handled && event.type() == WebEventType::RawKeyDown) + if (!handled && event->type() == WebEventType::RawKeyDown) dispatchPendingCharEvents(event); #endif bool canProcessMoreKeyEvents = !internals().keyEventQueue.isEmpty(); if (canProcessMoreKeyEvents && m_mainFrame) { - auto nextEvent = internals().keyEventQueue.first(); + Ref nextEvent = internals().keyEventQueue.first(); LOG(KeyHandling, " UI process: sent keyEvent from keyEventHandlingCompleted"); - sendKeyEvent(nextEvent); + sendKeyEvent(WTF::move(nextEvent)); } // The call to doneWithKeyEvent may close this WebPage. @@ -16534,7 +16535,7 @@ void WebPageProxy::cancelComposition(const String& compositionString) if (internals().keyEventQueue.size() > 1) { auto event = internals().keyEventQueue.takeFirst(); internals().keyEventQueue.removeAllMatching([](const auto& event) { - return event.handledByInputMethod(); + return event->handledByInputMethod(); }); internals().keyEventQueue.prepend(WTF::move(event)); } diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h index c76bcaf7453f..06a928c08849 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -1100,7 +1100,7 @@ class WebPageProxy final : public API::ObjectImpl, publ void viewWillStartLiveResize(); void viewWillEndLiveResize(); - void setInitialFocus(bool forward, bool isKeyboardEventValid, const std::optional&, CompletionHandler&&); + void setInitialFocus(bool forward, bool isKeyboardEventValid, RefPtr&&, CompletionHandler&&); void clearSelection(std::optional = std::nullopt); void restoreSelectionInFocusedEditableElement(); @@ -1375,7 +1375,7 @@ class WebPageProxy final : public API::ObjectImpl, publ void windowAndViewFramesChanged(const WebCore::FloatRect& viewFrameInWindowCoordinates, const WebCore::FloatPoint& accessibilityViewCoordinates); void setMainFrameIsScrollable(bool); - bool shouldDelayWindowOrderingForEvent(const WebMouseEvent&); + bool shouldDelayWindowOrderingForEvent(Ref&&); void setRemoteLayerTreeRootNode(RemoteLayerTreeNode*); #if PLATFORM(MAC) @@ -1470,10 +1470,10 @@ class WebPageProxy final : public API::ObjectImpl, publ bool NODELETE isProcessingMouseEvents() const; void processNextQueuedMouseEvent(); - void sendMouseEvent(WebCore::FrameIdentifier, const NativeWebMouseEvent&, std::optional>&&); - void handleMouseEvent(const NativeWebMouseEvent&); + void sendMouseEvent(WebCore::FrameIdentifier, Ref&&, std::optional>&&); + void handleMouseEvent(Ref&&); void recordUIProcessUserActivation(const WebEvent&); - void dispatchMouseDidMoveOverElementAsynchronously(const NativeWebMouseEvent&); + void dispatchMouseDidMoveOverElementAsynchronously(Ref&&); void doAfterProcessingAllPendingMouseEvents(Function&&); void didFinishProcessingAllPendingMouseEvents(); @@ -1495,32 +1495,32 @@ class WebPageProxy final : public API::ObjectImpl, publ #endif bool NODELETE isProcessingWheelEvents() const; - void handleNativeWheelEvent(const NativeWebWheelEvent&); + void handleNativeWheelEvent(Ref&&); void interruptSyntheticMomentumScrolling(); - void continueWheelEventHandling(const WebWheelEvent&, const WebCore::WheelEventHandlingResult&, std::optional willStartSwipe); + void continueWheelEventHandling(Ref&&, const WebCore::WheelEventHandlingResult&, std::optional willStartSwipe); void wheelEventHandlingCompleted(bool wasHandled); void didEndSyntheticMomentumScrolling(); bool NODELETE isProcessingKeyboardEvents() const; - void sendKeyEvent(const NativeWebKeyboardEvent&); - bool handleKeyboardEvent(const NativeWebKeyboardEvent&); + void sendKeyEvent(Ref&&); + bool handleKeyboardEvent(Ref&&); #if PLATFORM(WIN) void dispatchPendingCharEvents(const NativeWebKeyboardEvent&); #endif #if ENABLE(MAC_GESTURE_EVENTS) - void sendGestureEvent(WebCore::FrameIdentifier, const NativeWebGestureEvent&); - void handleGestureEvent(const NativeWebGestureEvent&); + void sendGestureEvent(WebCore::FrameIdentifier, Ref&&); + void handleGestureEvent(Ref&&); void processNextQueuedGestureEvent(); #endif #if ENABLE(IOS_TOUCH_EVENTS) void didBeginTouchPoint(WebCore::FloatPoint locationInRootView); void handlePreventableTouchEvent(NativeWebTouchEvent&); - void handleUnpreventableTouchEvent(const NativeWebTouchEvent&); + void handleUnpreventableTouchEvent(NativeWebTouchEvent&); #elif ENABLE(TOUCH_EVENTS) - void handleTouchEvent(IPC::Connection*, const NativeWebTouchEvent&); + void handleTouchEvent(IPC::Connection*, Ref&&); #endif #if PLATFORM(MAC) @@ -2636,14 +2636,14 @@ class WebPageProxy final : public API::ObjectImpl, publ #if PLATFORM(MAC) void changeUniversalAccessZoomFocus(const WebCore::IntRect&, const WebCore::IntRect&); - bool acceptsFirstMouse(int eventNumber, const WebMouseEvent&); + bool acceptsFirstMouse(int eventNumber, Ref&&); #endif bool isServiceWorkerPage() const { return m_isServiceWorkerPage; } #if PLATFORM(IOS_FAMILY) - void handleWheelEventWithoutScrolling(const WebWheelEvent&, CompletionHandler&&); - void sendWheelEventWithoutScrolling(WebCore::FrameIdentifier, const WebWheelEvent&, CompletionHandler&&); + void handleWheelEventWithoutScrolling(Ref&&, CompletionHandler&&); + void sendWheelEventWithoutScrolling(WebCore::FrameIdentifier, Ref&&, CompletionHandler&&); #endif #if ENABLE(CONTEXT_MENUS) && ENABLE(IMAGE_ANALYSIS) @@ -3502,8 +3502,8 @@ class WebPageProxy final : public API::ObjectImpl, publ void setRenderTreeSize(uint64_t treeSize) { m_renderTreeSize = treeSize; } - void handleWheelEvent(const WebWheelEvent&); - void sendWheelEvent(WebCore::FrameIdentifier, const WebWheelEvent&, OptionSet, WebCore::RectEdges rubberBandableEdges, std::optional willStartSwipe, bool wasHandledForScrolling); + void handleWheelEvent(Ref&&); + void sendWheelEvent(WebCore::FrameIdentifier, Ref&&, OptionSet, WebCore::RectEdges rubberBandableEdges, std::optional willStartSwipe, bool wasHandledForScrolling); void handleWheelEventReply(IPC::Connection*, const WebWheelEvent&, std::optional, std::optional, bool wasHandledForScrolling, bool wasHandledByWebProcess); void cacheWheelEventScrollingAccelerationCurve(const NativeWebWheelEvent&); @@ -3688,8 +3688,8 @@ class WebPageProxy final : public API::ObjectImpl, publ template decltype(auto) sendToWebPage(std::optional, F&&); - void sendPreventableTouchEvent(WebCore::FrameIdentifier, const WebTouchEvent&); - void sendUnpreventableTouchEvent(WebCore::FrameIdentifier, const WebTouchEvent&); + void sendPreventableTouchEvent(WebCore::FrameIdentifier, Ref&&); + void sendUnpreventableTouchEvent(WebCore::FrameIdentifier, Ref&&); void broadcastFocusedFrameToOtherProcesses(IPC::Connection&, std::optional&&); diff --git a/Source/WebKit/UIProcess/WebPageProxyInternals.h b/Source/WebKit/UIProcess/WebPageProxyInternals.h index 39c45248e575..65457ab5e40b 100644 --- a/Source/WebKit/UIProcess/WebPageProxyInternals.h +++ b/Source/WebKit/UIProcess/WebPageProxyInternals.h @@ -136,12 +136,12 @@ struct SpeechSynthesisData { #if ENABLE(TOUCH_EVENTS) struct QueuedTouchEvents { - QueuedTouchEvents(const NativeWebTouchEvent& event) - : forwardedEvent(event) + QueuedTouchEvents(Ref&& event) + : forwardedEvent(WTF::move(event)) { } - NativeWebTouchEvent forwardedEvent; - Vector deferredTouchEvents; + Ref forwardedEvent; + Vector> deferredTouchEvents; }; struct TouchEventTracking { @@ -236,7 +236,7 @@ struct WebPageProxy::Internals final : WebPopupMenuProxy::Client WebCore::IntSize fixedLayoutSize; GeolocationPermissionRequestManagerProxy geolocationPermissionRequestManager; HiddenPageThrottlingAutoIncreasesCounter::Token hiddenPageDOMTimerThrottlingAutoIncreasesCount; - Deque keyEventQueue; + Deque> keyEventQueue; WebCore::RectEdges mainFramePinnedState { true, true, true, true }; WebCore::LayoutPoint maxStableLayoutViewportOrigin; WebCore::FloatSize maximumUnobscuredSize; @@ -246,8 +246,8 @@ struct WebPageProxy::Internals final : WebPopupMenuProxy::Client WebCore::LayoutPoint minStableLayoutViewportOrigin; WebCore::IntSize minimumSizeForAutoLayout; WebCore::FloatSize minimumUnobscuredSize; - Deque mouseEventQueue; - Vector coalescedMouseEvents; + Deque> mouseEventQueue; + Vector> coalescedMouseEvents; WebCore::MediaProducerMutedStateFlags mutedState; WebNotificationManagerMessageHandler notificationManagerMessageHandler; OptionSet observedLayoutMilestones; @@ -333,7 +333,7 @@ struct WebPageProxy::Internals final : WebPopupMenuProxy::Client RefPtr colorPicker; #if ENABLE(MAC_GESTURE_EVENTS) - Deque gestureEventQueue; + Deque> gestureEventQueue; unsigned droppedGestureEventCount { 0 }; #endif diff --git a/Source/WebKit/UIProcess/gtk/PointerLockManager.cpp b/Source/WebKit/UIProcess/gtk/PointerLockManager.cpp index 99148dc7191c..f2b08b514936 100644 --- a/Source/WebKit/UIProcess/gtk/PointerLockManager.cpp +++ b/Source/WebKit/UIProcess/gtk/PointerLockManager.cpp @@ -97,7 +97,7 @@ bool PointerLockManager::unlock() void PointerLockManager::handleMotion(const FloatSize& delta) { - m_webPage.handleMouseEvent(NativeWebMouseEvent(WebEventType::MouseMove, m_button, m_buttons, IntPoint(m_position), IntPoint(m_initialPoint), 0, m_modifiers, delta, mousePointerID, mousePointerEventType(), PlatformMouseEvent::IsTouch::No)); + m_webPage.handleMouseEvent(NativeWebMouseEvent::create(WebEventType::MouseMove, m_button, m_buttons, IntPoint(m_position), IntPoint(m_initialPoint), 0, m_modifiers, delta, mousePointerID, mousePointerEventType(), PlatformMouseEvent::IsTouch::No)); } } // namespace WebKit diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm index acafd3ac6751..41d741eed045 100644 --- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm +++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm @@ -2268,8 +2268,8 @@ - (void)_touchEventsRecognized } #if ENABLE(TOUCH_EVENTS) - WebKit::NativeWebTouchEvent nativeWebTouchEvent { lastTouchEvent, [_touchEventGestureRecognizer modifierFlags] }; - nativeWebTouchEvent.setCanPreventNativeGestures(_touchEventsCanPreventNativeGestures || [_touchEventGestureRecognizer isDefaultPrevented]); + Ref nativeWebTouchEvent = WebKit::NativeWebTouchEvent::create(lastTouchEvent, [_touchEventGestureRecognizer modifierFlags]); + nativeWebTouchEvent->setCanPreventNativeGestures(_touchEventsCanPreventNativeGestures || [_touchEventGestureRecognizer isDefaultPrevented]); [self _handleTouchActionsForTouchEvent:nativeWebTouchEvent]; @@ -2281,14 +2281,14 @@ - (void)_touchEventsRecognized else _page->handleUnpreventableTouchEvent(nativeWebTouchEvent); - if (nativeWebTouchEvent.allTouchPointsAreReleased()) { + if (nativeWebTouchEvent->allTouchPointsAreReleased()) { _touchEventsCanPreventNativeGestures = YES; _touchStartedNearSelectionHandle = NO; if (!_page->isScrollingOrZooming()) [self _resetPanningPreventionFlags]; - if (nativeWebTouchEvent.isPotentialTap() && self.hasHiddenContentEditable && self._hasFocusedElement && !self.window.keyWindow) + if (nativeWebTouchEvent->isPotentialTap() && self.hasHiddenContentEditable && self._hasFocusedElement && !self.window.keyWindow) [self.window makeKeyWindow]; auto stopDeferringNativeGesturesIfNeeded = [] (WKDeferringGestureRecognizer *gestureRecognizer) { @@ -7782,7 +7782,7 @@ - (void)handleKeyWebEvent:(::WebEvent *)theEvent - (void)_internalHandleKeyWebEvent:(::WebEvent *)theEvent { - protect(_page)->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(theEvent, WebKit::NativeWebKeyboardEvent::HandledByInputMethod::No)); + protect(_page)->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(theEvent, WebKit::NativeWebKeyboardEvent::HandledByInputMethod::No)); } - (void)handleKeyWebEvent:(::WebEvent *)event withCompletionHandler:(void (^)(::WebEvent *theEvent, BOOL wasHandled))completionHandler @@ -7851,11 +7851,11 @@ - (void)_internalHandleKeyWebEvent:(::WebEvent *)event withCompletionHandler:(vo if ([self _deferKeyEventToInputMethodEditing:event]) { completionHandler(event, YES); _isDeferringKeyEventsToInputMethod = YES; - protect(_page)->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(event, HandledByInputMethod::Yes)); + protect(_page)->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(event, HandledByInputMethod::Yes)); return; } - if (protect(_page)->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(event, HandledByInputMethod::No))) + if (protect(_page)->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent::create(event, HandledByInputMethod::No))) _keyWebEventHandlers.append({ event, makeBlockPtr(completionHandler) }); else completionHandler(event, NO); @@ -12433,8 +12433,9 @@ - (void)setUpMouseGestureRecognizer [self _configureMouseGestureRecognizer]; } -- (void)mouseInteraction:(WKMouseInteraction *)interaction changedWithEvent:(const WebKit::NativeWebMouseEvent&)event +- (void)mouseInteraction:(WKMouseInteraction *)interaction changedWithEvent:(Ref&&)eventRef { + const auto& event = eventRef.get(); Ref page = *_page; if (!page->hasRunningProcess()) return; @@ -12451,7 +12452,7 @@ - (void)mouseInteraction:(WKMouseInteraction *)interaction changedWithEvent:(con [self.window makeKeyWindow]; } - page->handleMouseEvent(event); + page->handleMouseEvent(WTF::move(eventRef)); } #if ENABLE(POINTER_LOCK) diff --git a/Source/WebKit/UIProcess/ios/WKMouseInteraction.h b/Source/WebKit/UIProcess/ios/WKMouseInteraction.h index a30399ea5f36..d39506fc15e3 100644 --- a/Source/WebKit/UIProcess/ios/WKMouseInteraction.h +++ b/Source/WebKit/UIProcess/ios/WKMouseInteraction.h @@ -33,7 +33,7 @@ @class WKMouseInteraction; @protocol WKMouseInteractionDelegate -- (void)mouseInteraction:(WKMouseInteraction *)interaction changedWithEvent:(const WebKit::NativeWebMouseEvent&)event; +- (void)mouseInteraction:(WKMouseInteraction *)interaction changedWithEvent:(Ref&&)event; #if ENABLE(POINTER_LOCK) - (void)mouseInteractionDidLoseMouseDeviceDuringPointerLock:(WKMouseInteraction *)interaction; #endif diff --git a/Source/WebKit/UIProcess/ios/WKMouseInteraction.mm b/Source/WebKit/UIProcess/ios/WKMouseInteraction.mm index 97c67b789a23..425960aface1 100644 --- a/Source/WebKit/UIProcess/ios/WKMouseInteraction.mm +++ b/Source/WebKit/UIProcess/ios/WKMouseInteraction.mm @@ -255,10 +255,10 @@ inline static String pointerType(UITouchType type) return WebCore::mousePointerEventType(); } -- (std::optional)createMouseEventWithType:(std::optional)type wasCancelled:(BOOL)cancelled +- (RefPtr)createMouseEventWithType:(std::optional)type wasCancelled:(BOOL)cancelled { if (!type) - return std::nullopt; + return nullptr; auto modifiers = WebKit::WebIOSEventFactory::webEventModifiersForUIKeyModifierFlags(self._activeGesture.modifierFlags); UIEventButtonMask currentButtonMask = _pressedButtonMask.value_or(0); @@ -311,7 +311,7 @@ inline static String pointerType(UITouchType type) }(); auto delta = point - WebCore::DoublePoint { [currentTouch previousLocationInView:self.view] }; // UITouch's timestamp uses mach_absolute_time as its timebase, same as MonotonicTime. - return WebKit::NativeWebMouseEvent { + return WebKit::NativeWebMouseEvent::create( *type, button, static_cast(buttons), @@ -325,8 +325,7 @@ inline static String pointerType(UITouchType type) MonotonicTime::fromRawSeconds(currentTouch.timestamp), 0, cancelled ? WebKit::GestureWasCancelled::Yes : WebKit::GestureWasCancelled::No, - pointerType(currentTouch.type) - }; + pointerType(currentTouch.type)); } #pragma mark - UIInteraction @@ -406,7 +405,7 @@ - (void)_hoverGestureRecognized:(UIHoverGestureRecognizer *)gestureRecognizer _lastLocation = location; auto mouseEvent = [self createMouseEventWithType:WebKit::WebEventType::MouseMove wasCancelled:isCancelled]; if (mouseEvent) - [protect(_delegate) mouseInteraction:self changedWithEvent:*mouseEvent]; + [protect(_delegate) mouseInteraction:self changedWithEvent:mouseEvent.releaseNonNull()]; } - (void)_updateMouseTouches:(NSSet *)touches @@ -443,7 +442,7 @@ - (void)_updateMouseTouches:(NSSet *)touches return; if (eventType) - [protect(_delegate) mouseInteraction:self changedWithEvent:*mouseEvent]; + [protect(_delegate) mouseInteraction:self changedWithEvent:mouseEvent.releaseNonNull()]; if (eventType == WebKit::WebEventType::MouseUp) { _touching = NO; @@ -517,7 +516,7 @@ - (void)handleGameControllerMouseMove:(float)deltaX deltaY:(float)deltaY return; WebCore::DoublePoint lockedPoint { _pointerLockState.lockedCursorPosition.value_or(CGPointZero) }; - WebKit::NativeWebMouseEvent mouseEvent { + Ref mouseEvent = WebKit::NativeWebMouseEvent::create( WebKit::WebEventType::MouseMove, WebKit::WebMouseEventButton::None, 0, @@ -531,10 +530,9 @@ - (void)handleGameControllerMouseMove:(float)deltaX deltaY:(float)deltaY MonotonicTime::now(), 0, WebKit::GestureWasCancelled::No, - WebCore::mousePointerEventType() - }; + WebCore::mousePointerEventType()); - [protect(_delegate) mouseInteraction:self changedWithEvent:mouseEvent]; + [protect(_delegate) mouseInteraction:self changedWithEvent:WTF::move(mouseEvent)]; } - (void)_startObservingMouseNotifications diff --git a/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm b/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm index 9db09d9fe6b2..5a9a87a461f1 100644 --- a/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm +++ b/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm @@ -782,7 +782,7 @@ static inline float adjustedUnexposedMaxEdge(float documentEdge, float exposedRe completionHandler(false); } -bool WebPageProxy::shouldDelayWindowOrderingForEvent(const WebKit::WebMouseEvent&) +bool WebPageProxy::shouldDelayWindowOrderingForEvent(Ref&&) { notImplemented(); return false; diff --git a/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm b/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm index 9b93b5fa9b8c..80f27fa3a0a8 100644 --- a/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm +++ b/Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm @@ -1513,26 +1513,23 @@ - (void)sendWheelEventForGesture:(NSPanGestureRecognizer *)gesture auto makeWheelEvent = [&](WebCore::FloatSize delta) { auto wheelTicks { delta.scaled(1. / static_cast(WebCore::Scrollbar::pixelsPerLineStep())) }; auto unacceleratedScrollingDelta = delta; - return WebKit::NativeWebWheelEvent { - WebKit::WebWheelEvent { - { WebKit::WebEventType::Wheel, { }, timestamp, WTF::UUID::createVersion4() }, - WebCore::IntPoint { position }, - WebCore::IntPoint { globalPosition }, - delta, - wheelTicks, - granularity, - directionInvertedFromDevice, - phase, - momentumPhase, - hasPreciseScrollingDeltas, - scrollCount, - unacceleratedScrollingDelta, - ioHIDEventTimestamp, - rawPlatformDelta, - momentumEndType, - WebKit::WebEventInputSource::Automation - } - }; + return WebKit::NativeWebWheelEvent::create(WebKit::WebWheelEvent::create({ WebKit::WebEventType::Wheel, { }, timestamp }, { + .position = WebCore::IntPoint { position }, + .globalPosition = WebCore::IntPoint { globalPosition }, + .delta = delta, + .wheelTicks = wheelTicks, + .granularity = granularity, + .directionInvertedFromDevice = directionInvertedFromDevice, + .phase = phase, + .momentumPhase = momentumPhase, + .hasPreciseScrollingDeltas = hasPreciseScrollingDeltas, + .scrollCount = scrollCount, + .unacceleratedScrollingDelta = unacceleratedScrollingDelta, + .ioHIDEventTimestamp = ioHIDEventTimestamp, + .rawPlatformDelta = rawPlatformDelta, + .momentumEndType = momentumEndType, + .inputSource = WebKit::WebEventInputSource::Automation, + })); }; CheckedPtr impl = [webView _impl]; @@ -1589,28 +1586,24 @@ - (void)startMomentumIfNeededForGesture:(NSPanGestureRecognizer *)gesture WebCore::IntPoint position { [gesture locationInView:webView.get()] }; auto globalPosition = WebCore::globalPoint([gesture locationInView:nil], [webView window]); - WebKit::WebWheelEvent momentumEvent { - { WebKit::WebEventType::Wheel, { }, timestamp, WTF::UUID::createVersion4() }, - position, - WebCore::IntPoint { globalPosition }, - WebCore::FloatSize { }, - WebCore::FloatSize { }, - WebKit::WebWheelEvent::Granularity::ScrollByPixelWheelEvent, - false, - WebKit::WebWheelEvent::Phase::None, - WebKit::WebWheelEvent::Phase::Began, - true, - 1, - WebCore::FloatSize { }, - timestamp, - std::nullopt, - WebKit::WebWheelEvent::MomentumEndType::Unknown, - WebKit::WebEventInputSource::Automation, - static_cast(fastScrollMultiplier), - }; - WebKit::NativeWebWheelEvent nativeMomentumEvent { momentumEvent }; + Ref momentumEvent = WebKit::WebWheelEvent::create({ WebKit::WebEventType::Wheel, { }, timestamp }, { + .position = position, + .globalPosition = WebCore::IntPoint { globalPosition }, + .granularity = WebKit::WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .directionInvertedFromDevice = false, + .phase = WebKit::WebWheelEvent::Phase::None, + .momentumPhase = WebKit::WebWheelEvent::Phase::Began, + .hasPreciseScrollingDeltas = true, + .scrollCount = 1, + .ioHIDEventTimestamp = timestamp, + .rawPlatformDelta = std::nullopt, + .momentumEndType = WebKit::WebWheelEvent::MomentumEndType::Unknown, + .inputSource = WebKit::WebEventInputSource::Automation, + .momentumFastScrollMultiplier = static_cast(fastScrollMultiplier), + }); + Ref nativeMomentumEvent = WebKit::NativeWebWheelEvent::create(momentumEvent); - nativeMomentumEvent.setRawPlatformDelta([&nativeMomentumEvent, velocity] { + nativeMomentumEvent->setRawPlatformDelta([&nativeMomentumEvent, velocity] { static constexpr WebCore::FramesPerSecond fallbackMomentumFrameRate { 60 }; auto momentumFrameRate = WebKit::ScrollingAccelerationCurve::fromNativeWheelEvent(nativeMomentumEvent) .or_else([] { @@ -1627,7 +1620,7 @@ - (void)startMomentumIfNeededForGesture:(NSPanGestureRecognizer *)gesture protect([webView _impl])->clearRefreshControllerTracking(); #endif - [webView _protectedPage]->handleNativeWheelEvent(nativeMomentumEvent); + [webView _protectedPage]->handleNativeWheelEvent(WTF::move(nativeMomentumEvent)); _isMomentumActive = true; WK_APPKIT_GESTURE_CONTROLLER_RELEASE_LOG([webView _protectedPage]->logIdentifier(), "Started momentum scrolling with velocity %.2f pts/s", velocityMagnitude); diff --git a/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm b/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm index 47221c145683..c4a661883a78 100644 --- a/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm +++ b/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm @@ -266,17 +266,17 @@ static float maximumRectangleComponentDelta(FloatRect a, FloatRect b) m_lastMagnificationGestureWasSmartMagnification = true; } -bool ViewGestureController::PendingSwipeTracker::scrollEventCanStartSwipe(NativeWebWheelEvent event) +bool ViewGestureController::PendingSwipeTracker::scrollEventCanStartSwipe(const NativeWebWheelEvent& event) { return event.phase() == WebWheelEvent::Phase::Began; } -bool ViewGestureController::PendingSwipeTracker::scrollEventCanEndSwipe(NativeWebWheelEvent event) +bool ViewGestureController::PendingSwipeTracker::scrollEventCanEndSwipe(const NativeWebWheelEvent& event) { return event.phase() == WebWheelEvent::Phase::Ended; } -bool ViewGestureController::PendingSwipeTracker::scrollEventCanInfluenceSwipe(NativeWebWheelEvent event) +bool ViewGestureController::PendingSwipeTracker::scrollEventCanInfluenceSwipe(const NativeWebWheelEvent& event) { if (!event.hasPreciseScrollingDeltas()) return false; @@ -287,12 +287,12 @@ static float maximumRectangleComponentDelta(FloatRect a, FloatRect b) #endif } -FloatSize ViewGestureController::PendingSwipeTracker::scrollEventGetScrollingDeltas(NativeWebWheelEvent event) +FloatSize ViewGestureController::PendingSwipeTracker::scrollEventGetScrollingDeltas(const NativeWebWheelEvent& event) { return event.delta(); } -bool ViewGestureController::handleScrollWheelEvent(NativeWebWheelEvent event) +bool ViewGestureController::handleScrollWheelEvent(const NativeWebWheelEvent& event) { if (m_swipeProgressTracker && protect(*m_swipeProgressTracker)->handleEvent(event)) return true; diff --git a/Source/WebKit/UIProcess/mac/WKFullScreenWindowController.mm b/Source/WebKit/UIProcess/mac/WKFullScreenWindowController.mm index 6f99855d4efc..fb130a56f330 100644 --- a/Source/WebKit/UIProcess/mac/WKFullScreenWindowController.mm +++ b/Source/WebKit/UIProcess/mac/WKFullScreenWindowController.mm @@ -564,8 +564,8 @@ - (void)finishedEnterFullScreenAnimation:(bool)completed eventNumber:0 clickCount:0 pressure:0]; - WebKit::NativeWebMouseEvent webEvent(fakeEvent.get(), nil, webView.get(), WebKit::WebEventInputSource::UserDriven); - page->handleMouseEvent(webEvent); + Ref webEvent = WebKit::NativeWebMouseEvent::create(fakeEvent.get(), nil, webView.get(), WebKit::WebEventInputSource::UserDriven); + page->handleMouseEvent(WTF::move(webEvent)); } page->flushDeferredResizeEvents(); page->flushDeferredScrollEvents(); diff --git a/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm b/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm index 36f5caa9eca1..3b692ffbc9ad 100644 --- a/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm +++ b/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm @@ -412,18 +412,18 @@ static inline bool expectsLegacyImplicitRubberBandControl() completionHandler(pageClient->executeSavedCommandBySelector(selector)); } -bool WebPageProxy::shouldDelayWindowOrderingForEvent(const WebKit::WebMouseEvent& event) +bool WebPageProxy::shouldDelayWindowOrderingForEvent(Ref&& event) { if (legacyMainFrameProcess().state() != WebProcessProxy::State::Running) return false; const Seconds messageTimeout(3); - auto sendResult = protect(legacyMainFrameProcess())->sendSync(Messages::WebPage::ShouldDelayWindowOrderingEvent(event), webPageIDInMainFrameProcess(), messageTimeout); + auto sendResult = protect(legacyMainFrameProcess())->sendSync(Messages::WebPage::ShouldDelayWindowOrderingEvent(WTF::move(event)), webPageIDInMainFrameProcess(), messageTimeout); auto [result] = sendResult.takeReplyOr(false); return result; } -bool WebPageProxy::acceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event) +bool WebPageProxy::acceptsFirstMouse(int eventNumber, Ref&& event) { if (!hasRunningProcess()) return false; @@ -435,7 +435,7 @@ static inline bool expectsLegacyImplicitRubberBandControl() if (shouldAvoidSynchronouslyWaitingToPreventDeadlock()) return false; - legacyMainFrameProcess->send(Messages::WebPage::RequestAcceptsFirstMouse(eventNumber, event), webPageIDInMainFrameProcess(), IPC::SendOption::DispatchMessageEvenWhenWaitingForUnboundedSyncReply); + legacyMainFrameProcess->send(Messages::WebPage::RequestAcceptsFirstMouse(eventNumber, WTF::move(event)), webPageIDInMainFrameProcess(), IPC::SendOption::DispatchMessageEvenWhenWaitingForUnboundedSyncReply); bool receivedReply = protect(legacyMainFrameProcess->connection())->waitForAndDispatchImmediately(webPageIDInMainFrameProcess(), 250_ms, IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives) == IPC::Error::NoError; if (!receivedReply) { @@ -1213,25 +1213,19 @@ static inline bool expectsLegacyImplicitRubberBandControl() void WebPageProxy::interruptSyntheticMomentumScrolling() { auto timestamp = MonotonicTime::now(); - WebWheelEvent cancelEvent { - { WebEventType::Wheel, { }, timestamp, WTF::UUID::createVersion4() }, - WebCore::IntPoint { }, - WebCore::IntPoint { }, - WebCore::FloatSize { }, - WebCore::FloatSize { }, - WebWheelEvent::Granularity::ScrollByPixelWheelEvent, - false, - WebWheelEvent::Phase::Cancelled, - WebWheelEvent::Phase::None, - true, - 1, - WebCore::FloatSize { }, - timestamp, - std::nullopt, - WebWheelEvent::MomentumEndType::Interrupted, - WebEventInputSource::Automation - }; - handleNativeWheelEvent(NativeWebWheelEvent { cancelEvent }); + Ref cancelEvent = WebWheelEvent::create({ WebEventType::Wheel, { }, timestamp }, { + .granularity = WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .directionInvertedFromDevice = false, + .phase = WebWheelEvent::Phase::Cancelled, + .momentumPhase = WebWheelEvent::Phase::None, + .hasPreciseScrollingDeltas = true, + .scrollCount = 1, + .ioHIDEventTimestamp = timestamp, + .rawPlatformDelta = std::nullopt, + .momentumEndType = WebWheelEvent::MomentumEndType::Interrupted, + .inputSource = WebEventInputSource::Automation, + }); + handleNativeWheelEvent(NativeWebWheelEvent::create(cancelEvent)); } } // namespace WebKit diff --git a/Source/WebKit/UIProcess/mac/WebViewImpl.mm b/Source/WebKit/UIProcess/mac/WebViewImpl.mm index 5e9ce1f1b37c..b1de0c572813 100644 --- a/Source/WebKit/UIProcess/mac/WebViewImpl.mm +++ b/Source/WebKit/UIProcess/mac/WebViewImpl.mm @@ -1649,7 +1649,7 @@ static NSTrackingAreaOptions NODELETE flagsChangedEventMonitorTrackingAreaOption RetainPtr keyboardEvent; if ([event type] == NSEventTypeKeyDown || [event type] == NSEventTypeKeyUp) keyboardEvent = event; - m_page->setInitialFocus(direction == NSSelectingNext, !!keyboardEvent, NativeWebKeyboardEvent(keyboardEvent.get(), false, false, { }), [] { }); + m_page->setInitialFocus(direction == NSSelectingNext, !!keyboardEvent, NativeWebKeyboardEvent::create(keyboardEvent.get(), false, false, { }), [] { }); } return true; } @@ -2530,8 +2530,8 @@ static WKPDFHUDViewAccessibilityDisplayModeState platformAccessibilityDisplayMod return false; auto previousEvent = setLastMouseDownEvent(event); - WebMouseEvent mouseEvent = WebEventFactory::createWebMouseEvent(event, m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven); - bool result = m_page->acceptsFirstMouse(event.eventNumber, mouseEvent); + Ref mouseEvent = WebMouseEvent::create(WebEventFactory::createWebMouseEvent(event, m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven)); + bool result = m_page->acceptsFirstMouse(event.eventNumber, WTF::move(mouseEvent)); setLastMouseDownEvent(previousEvent.get()); return result; } @@ -2559,8 +2559,8 @@ static WKPDFHUDViewAccessibilityDisplayModeState platformAccessibilityDisplayMod } auto previousEvent = setLastMouseDownEvent(event); - WebMouseEvent mouseEvent = WebEventFactory::createWebMouseEvent(event, m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven); - bool result = m_page->shouldDelayWindowOrderingForEvent(mouseEvent); + Ref mouseEvent = WebMouseEvent::create(WebEventFactory::createWebMouseEvent(event, m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven)); + bool result = m_page->shouldDelayWindowOrderingForEvent(WTF::move(mouseEvent)); setLastMouseDownEvent(previousEvent.get()); return result; } @@ -2828,8 +2828,8 @@ static WKPDFHUDViewAccessibilityDisplayModeState platformAccessibilityDisplayMod RetainPtr fakeEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved location:flagsChangedEvent.window.mouseLocationOutsideOfEventStream modifierFlags:flagsChangedEvent.modifierFlags timestamp:flagsChangedEvent.timestamp windowNumber:flagsChangedEvent.windowNumber context:nullptr eventNumber:0 clickCount:0 pressure:0]; - NativeWebMouseEvent webEvent(fakeEvent.get(), m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven); - m_page->dispatchMouseDidMoveOverElementAsynchronously(webEvent); + Ref webEvent = NativeWebMouseEvent::create(fakeEvent.get(), m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven); + m_page->dispatchMouseDidMoveOverElementAsynchronously(WTF::move(webEvent)); } WebCore::DestinationColorSpace WebViewImpl::colorSpace() @@ -3032,8 +3032,8 @@ static WKPDFHUDViewAccessibilityDisplayModeState platformAccessibilityDisplayMod if (event.phase != NSEventPhaseChanged && event.phase != NSEventPhaseBegan && event.phase != NSEventPhaseEnded) return; - NativeWebMouseEvent webEvent(event, m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven); - m_page->handleMouseEvent(webEvent); + Ref webEvent = NativeWebMouseEvent::create(event, m_lastPressureEvent.get(), m_view.get().get(), WebEventInputSource::UserDriven); + m_page->handleMouseEvent(WTF::move(webEvent)); m_lastPressureEvent = event; } @@ -5712,7 +5712,7 @@ static NSPasteboardName NODELETE pasteboardNameForAccessCategory(WebCore::DOMPas bool wasIgnoringPinnedState = gestureController->shouldIgnorePinnedState(); gestureController->setShouldIgnorePinnedState(ignoringPinnedState); - NativeWebWheelEvent webEvent { event, m_view.getAutoreleased() }; + Ref webEvent = NativeWebWheelEvent::create(event, m_view.getAutoreleased()); bool handledEvent = gestureController->handleScrollWheelEvent(webEvent); gestureController->setShouldIgnorePinnedState(wasIgnoringPinnedState); @@ -5742,14 +5742,14 @@ static NSPasteboardName NODELETE pasteboardNameForAccessCategory(WebCore::DOMPas updateRefreshControllerForWheelEvent(event); #endif - NativeWebWheelEvent webEvent { event, m_view.getAutoreleased() }; + Ref webEvent = NativeWebWheelEvent::create(event, m_view.getAutoreleased()); if (m_allowsBackForwardNavigationGestures && protect(ensureGestureController())->handleScrollWheelEvent(webEvent)) { RELEASE_LOG(MouseHandling, "[pageProxyID=%lld] WebViewImpl::scrollWheel: Gesture controller handled wheel event", m_page->identifier().toUInt64()); return; } - m_page->handleNativeWheelEvent(webEvent); + m_page->handleNativeWheelEvent(WTF::move(webEvent)); } void WebViewImpl::swipeWithEvent(NSEvent *event) @@ -6783,7 +6783,7 @@ static BOOL shouldUseHighlightsForMarkedText(NSAttributedString *string) if (m_view.getAutoreleased() == [m_view.get() window].firstResponder) { interpretKeyEvent(event, [weakThis = WeakPtr { *this }, capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector& commands) { if (weakThis) - weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, false, commands)); + weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent::create(capturedEvent.get(), handledByInputMethod, false, commands)); }); return YES; } @@ -6801,7 +6801,7 @@ static BOOL shouldUseHighlightsForMarkedText(NSAttributedString *string) m_isTextInsertionReplacingSoftSpace = false; interpretKeyEvent(event, [weakThis = WeakPtr { *this }, capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector& commands) { if (weakThis) - weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, weakThis->m_isTextInsertionReplacingSoftSpace, commands)); + weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent::create(capturedEvent.get(), handledByInputMethod, weakThis->m_isTextInsertionReplacingSoftSpace, commands)); }); } @@ -6824,7 +6824,7 @@ static BOOL shouldUseHighlightsForMarkedText(NSAttributedString *string) m_isTextInsertionReplacingSoftSpace = false; interpretKeyEvent(event, [weakThis = WeakPtr { *this }, capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector& commands) { if (weakThis) - weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, weakThis->m_isTextInsertionReplacingSoftSpace, commands)); + weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent::create(capturedEvent.get(), handledByInputMethod, weakThis->m_isTextInsertionReplacingSoftSpace, commands)); }); } @@ -6841,7 +6841,7 @@ static BOOL shouldUseHighlightsForMarkedText(NSAttributedString *string) interpretKeyEvent(event, [weakThis = WeakPtr { *this }, capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector& commands) { if (weakThis) - weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, false, commands)); + weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent::create(capturedEvent.get(), handledByInputMethod, false, commands)); }); } @@ -6888,14 +6888,14 @@ static BOOL shouldUseHighlightsForMarkedText(NSAttributedString *string) if (handled) LOG_WITH_STREAM(TextInput, stream << "Event " << [retainedEvent type] << " was handled by text input context"); else { - NativeWebMouseEvent webEvent(retainedEvent.get(), weakThis->m_lastPressureEvent.get(), weakThis->m_view.getAutoreleased(), inputSource, canInitiateDrag); - weakThis->m_page->handleMouseEvent(webEvent); + Ref webEvent = NativeWebMouseEvent::create(retainedEvent.get(), weakThis->m_lastPressureEvent.get(), weakThis->m_view.getAutoreleased(), inputSource, canInitiateDrag); + weakThis->m_page->handleMouseEvent(WTF::move(webEvent)); } }]; return; } - NativeWebMouseEvent webEvent(event, m_lastPressureEvent.get(), m_view.get().get(), inputSource, canInitiateDrag); - m_page->handleMouseEvent(webEvent); + Ref webEvent = NativeWebMouseEvent::create(event, m_lastPressureEvent.get(), m_view.get().get(), inputSource, canInitiateDrag); + m_page->handleMouseEvent(WTF::move(webEvent)); } void WebViewImpl::nativeMouseEventHandlerInternal(NSEvent *event, WebEventInputSource inputSource, WebCore::PlatformMouseEvent::CanInitiateDrag canInitiateDrag) diff --git a/Source/WebKit/UIProcess/win/WebPageProxyWin.cpp b/Source/WebKit/UIProcess/win/WebPageProxyWin.cpp index 421ef3e17369..b5788f257d63 100644 --- a/Source/WebKit/UIProcess/win/WebPageProxyWin.cpp +++ b/Source/WebKit/UIProcess/win/WebPageProxyWin.cpp @@ -82,11 +82,11 @@ void WebPageProxy::dispatchPendingCharEvents(const NativeWebKeyboardEvent& keydo { auto& pendingCharEvents = keydownEvent.pendingCharEvents(); for (auto it = pendingCharEvents.rbegin(); it != pendingCharEvents.rend(); it++) { - auto nativeKeyPressEvent = NativeWebKeyboardEvent(it->hwnd, it->message, it->wParam, it->lParam, { }); + Ref nativeKeyPressEvent = NativeWebKeyboardEvent::create(it->hwnd, it->message, it->wParam, it->lParam, { }); // Allows keypresses generated by the char event to be matched to // the originating keydown by its keycode - nativeKeyPressEvent.setWindowsVirtualKeyCode(keydownEvent.windowsVirtualKeyCode()); - internals().keyEventQueue.prepend(nativeKeyPressEvent); + nativeKeyPressEvent->setWindowsVirtualKeyCode(keydownEvent.windowsVirtualKeyCode()); + internals().keyEventQueue.prepend(WTF::move(nativeKeyPressEvent)); } } diff --git a/Source/WebKit/UIProcess/win/WebView.cpp b/Source/WebKit/UIProcess/win/WebView.cpp index f3beb2f8390b..af4bab9f284e 100644 --- a/Source/WebKit/UIProcess/win/WebView.cpp +++ b/Source/WebKit/UIProcess/win/WebView.cpp @@ -341,7 +341,7 @@ void WebView::windowAncestryDidChange() LRESULT WebView::onMouseEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) { - NativeWebMouseEvent mouseEvent = NativeWebMouseEvent(hWnd, message, wParam, lParam, m_wasActivatedByMouseEvent, m_page->intrinsicDeviceScaleFactor()); + Ref mouseEvent = NativeWebMouseEvent::create(hWnd, message, wParam, lParam, m_wasActivatedByMouseEvent, m_page->intrinsicDeviceScaleFactor()); setWasActivatedByMouseEvent(false); switch (message) { @@ -370,7 +370,7 @@ LRESULT WebView::onMouseEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa ASSERT_NOT_REACHED(); } - m_page->handleMouseEvent(mouseEvent); + m_page->handleMouseEvent(WTF::move(mouseEvent)); handled = true; return 0; @@ -378,15 +378,15 @@ LRESULT WebView::onMouseEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa LRESULT WebView::onWheelEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) { - NativeWebWheelEvent wheelEvent(hWnd, message, wParam, lParam, m_page->intrinsicDeviceScaleFactor()); - if (wheelEvent.controlKey()) { + Ref wheelEvent = NativeWebWheelEvent::create(hWnd, message, wParam, lParam, m_page->intrinsicDeviceScaleFactor()); + if (wheelEvent->controlKey()) { // We do not want WebKit to handle Control + Wheel, this should be handled by the client application // to zoom the page. handled = false; return 0; } - m_page->handleNativeWheelEvent(wheelEvent); + m_page->handleNativeWheelEvent(WTF::move(wheelEvent)); handled = true; return 0; @@ -467,7 +467,7 @@ LRESULT WebView::onKeyEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara pendingCharEvents.append(msg); } } - m_page->handleKeyboardEvent(NativeWebKeyboardEvent(hWnd, message, wParam, lParam, WTF::move(pendingCharEvents))); + m_page->handleKeyboardEvent(NativeWebKeyboardEvent::create(hWnd, message, wParam, lParam, WTF::move(pendingCharEvents))); // We claim here to always have handled the event. If the event is not in fact handled, we will // find out later in didNotHandleKeyEvent. diff --git a/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm b/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm index 9b9e203ba6be..7c82423e6d24 100644 --- a/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm +++ b/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm @@ -1090,7 +1090,14 @@ static bool getEventTypeFromWebEvent(const WebEvent& event, NSEventType& eventTy if (!frameView) return false; IntPoint contentsPoint = frameView->contentsToRootView(point); - WebMouseEvent event({ WebEventType::MouseDown, OptionSet { }, MonotonicTime::now() }, WebMouseEventButton::Right, 0, contentsPoint, contentsPoint, 0, 0, 0, 1, WebCore::ForceAtClick); + Ref event = WebMouseEvent::create({ WebEventType::MouseDown, OptionSet { }, MonotonicTime::now() }, { + .button = WebMouseEventButton::Right, + .buttons = 0, + .position = contentsPoint, + .globalPosition = contentsPoint, + .clickCount = 1, + .force = WebCore::ForceAtClick, + }); return handleContextMenuEvent(event); } diff --git a/Source/WebKit/WebProcess/Plugins/PDF/PDFPluginBase.mm b/Source/WebKit/WebProcess/Plugins/PDF/PDFPluginBase.mm index 3912a3ca000c..8d6549e941e8 100644 --- a/Source/WebKit/WebProcess/Plugins/PDF/PDFPluginBase.mm +++ b/Source/WebKit/WebProcess/Plugins/PDF/PDFPluginBase.mm @@ -1345,7 +1345,15 @@ if (!frameView) return false; IntPoint contentsPoint = frameView->contentsToRootView(point); - WebMouseEvent event({ WebEventType::MouseDown, OptionSet { }, MonotonicTime::now() }, WebMouseEventButton::Right, 0, contentsPoint, contentsPoint, 0, 0, 0, 1, WebCore::ForceAtClick, WebEventInputSource::UserDriven); + Ref event = WebMouseEvent::create({ WebEventType::MouseDown, OptionSet { }, MonotonicTime::now() }, { + .button = WebMouseEventButton::Right, + .buttons = 0, + .position = contentsPoint, + .globalPosition = contentsPoint, + .clickCount = 1, + .force = WebCore::ForceAtClick, + .inputSource = WebEventInputSource::UserDriven, + }); return handleContextMenuEvent(event); } diff --git a/Source/WebKit/WebProcess/Plugins/PluginView.cpp b/Source/WebKit/WebProcess/Plugins/PluginView.cpp index 1776b6d91044..df625e40caa1 100644 --- a/Source/WebKit/WebProcess/Plugins/PluginView.cpp +++ b/Source/WebKit/WebProcess/Plugins/PluginView.cpp @@ -725,7 +725,7 @@ void PluginView::handleEvent(Event& event) if (!shouldForwardToPlugin(event)) return; - const CheckedPtr currentEvent = WebPage::currentEvent(); + const RefPtr currentEvent = WebPage::currentEvent(); if (!currentEvent) return; diff --git a/Source/WebKit/WebProcess/WebPage/EventDispatcher.cpp b/Source/WebKit/WebProcess/WebPage/EventDispatcher.cpp index 0fd4a986ace8..4a8f2e6ff336 100644 --- a/Source/WebKit/WebProcess/WebPage/EventDispatcher.cpp +++ b/Source/WebKit/WebProcess/WebPage/EventDispatcher.cpp @@ -168,7 +168,7 @@ void EventDispatcher::internalWheelEvent(PageIdentifier pageID, const WebWheelEv scrollingTree->willProcessWheelEvent(); - ScrollingThread::dispatch([scrollingTree, wheelEvent, platformWheelEvent, processingSteps, useMainThreadForScrolling, pageID, this, protectedThis = Ref { *this }, completionHandler = WTF::move(completionHandler)] mutable { + ScrollingThread::dispatch([scrollingTree, wheelEvent = Ref { wheelEvent }, platformWheelEvent, processingSteps, useMainThreadForScrolling, pageID, this, protectedThis = Ref { *this }, completionHandler = WTF::move(completionHandler)] mutable { if (useMainThreadForScrolling) { scrollingTree->willSendEventToMainThread(platformWheelEvent); dispatchWheelEventViaMainThread(pageID, wheelEvent, processingSteps, WTF::move(completionHandler)); @@ -198,7 +198,7 @@ void EventDispatcher::internalWheelEvent(PageIdentifier pageID, const WebWheelEv #endif } -void EventDispatcher::wheelEvent(PageIdentifier pageID, const WebWheelEvent& wheelEvent, RectEdges rubberBandableEdges, CompletionHandler&& completionHandler) +void EventDispatcher::wheelEvent(PageIdentifier pageID, Ref&& wheelEvent, RectEdges rubberBandableEdges, CompletionHandler&& completionHandler) { #if ENABLE(MOMENTUM_EVENT_DISPATCHER) if (m_momentumEventDispatcher->handleWheelEvent(pageID, wheelEvent, rubberBandableEdges)) { @@ -210,18 +210,18 @@ void EventDispatcher::wheelEvent(PageIdentifier pageID, const WebWheelEvent& whe } #if ENABLE(MAC_GESTURE_EVENTS) -void EventDispatcher::gestureEvent(FrameIdentifier frameID, PageIdentifier pageID, const WebGestureEvent& gestureEvent, CompletionHandler, bool, std::optional)>&& completionHandler) +void EventDispatcher::gestureEvent(FrameIdentifier frameID, PageIdentifier pageID, Ref&& gestureEvent, CompletionHandler, bool, std::optional)>&& completionHandler) { - RunLoop::mainSingleton().dispatch([this, frameID, pageID, gestureEvent, completionHandler = WTF::move(completionHandler)] mutable { + RunLoop::mainSingleton().dispatch([this, frameID, pageID, gestureEvent = WTF::move(gestureEvent), completionHandler = WTF::move(completionHandler)] mutable { dispatchGestureEvent(frameID, pageID, gestureEvent, WTF::move(completionHandler)); }); } #endif #if ENABLE(IOS_TOUCH_EVENTS) -TouchEventData::TouchEventData(WebCore::FrameIdentifier frameID, const WebTouchEvent& event, CompletionHandler)>&& completionHandler) +TouchEventData::TouchEventData(WebCore::FrameIdentifier frameID, Ref&& event, CompletionHandler)>&& completionHandler) : frameID(frameID) - , event(event) + , event(WTF::move(event)) { completionHandlers.append(WTF::move(completionHandler)); } @@ -240,7 +240,7 @@ void EventDispatcher::takeQueuedTouchEventsForPage(const WebPage& webPage, Uniqu destinationQueue = makeUniqueRefFromNonNullUniquePtr(WTF::move(queue)); } -void EventDispatcher::touchEvent(PageIdentifier pageID, FrameIdentifier frameID, const WebTouchEvent& touchEvent, CompletionHandler)>&& completionHandler) +void EventDispatcher::touchEvent(PageIdentifier pageID, FrameIdentifier frameID, Ref&& touchEvent, CompletionHandler)>&& completionHandler) { bool updateListWasEmpty; { @@ -248,26 +248,27 @@ void EventDispatcher::touchEvent(PageIdentifier pageID, FrameIdentifier frameID, updateListWasEmpty = m_touchEvents.isEmpty(); auto addResult = m_touchEvents.add(pageID, makeUniqueRef()); if (addResult.isNewEntry) - addResult.iterator->value->append({ frameID, touchEvent, WTF::move(completionHandler) }); + addResult.iterator->value->append({ frameID, WTF::move(touchEvent), WTF::move(completionHandler) }); else { auto& queuedEvents = addResult.iterator->value; ASSERT(!queuedEvents->isEmpty()); auto& touchEventData = queuedEvents->last(); // Coalesce touch move events. - if (touchEvent.type() == WebEventType::TouchMove && touchEventData.event.type() == WebEventType::TouchMove) { - auto coalescedEvents = Vector { }; - coalescedEvents.appendVector(queuedEvents->last().event.coalescedEvents()); - coalescedEvents.appendVector(touchEvent.coalescedEvents()); + if (touchEvent->type() == WebEventType::TouchMove && touchEventData.event->type() == WebEventType::TouchMove) { + auto coalescedEvents = Vector> { }; + coalescedEvents.appendVector(queuedEvents->last().event->coalescedEvents()); + coalescedEvents.appendVector(touchEvent->coalescedEvents()); - auto touchEventWithCoalescedEvents = touchEvent; - touchEventWithCoalescedEvents.setCoalescedEvents(coalescedEvents); + // A copy: the caller's event must not be mutated now that events are shared. + Ref touchEventWithCoalescedEvents = touchEvent->copy(); + touchEventWithCoalescedEvents->setCoalescedEvents(coalescedEvents); // Preserve coalesced completion handlers so their state transitions are not lost. queuedEvents->last().frameID = frameID; - queuedEvents->last().event = touchEventWithCoalescedEvents; + queuedEvents->last().event = WTF::move(touchEventWithCoalescedEvents); queuedEvents->last().completionHandlers.append(WTF::move(completionHandler)); } else - queuedEvents->append({ frameID, touchEvent, WTF::move(completionHandler) }); + queuedEvents->append({ frameID, WTF::move(touchEvent), WTF::move(completionHandler) }); } } @@ -302,19 +303,19 @@ void EventDispatcher::dispatchTouchEvents() } } #elif ENABLE(COORDINATED_TOUCH_EVENTS) -void EventDispatcher::dispatchTouchEventViaMainThread(WebCore::PageIdentifier pageID, const WebTouchEvent& touchEvent, CompletionHandler&& completionHandler) +void EventDispatcher::dispatchTouchEventViaMainThread(WebCore::PageIdentifier pageID, Ref&& touchEvent, CompletionHandler&& completionHandler) { - RunLoop::mainSingleton().dispatch([protectedThis = Ref { *this }, pageID, touchEvent, completionHandler = WTF::move(completionHandler)] mutable { + RunLoop::mainSingleton().dispatch([protectedThis = Ref { *this }, pageID, touchEvent = WTF::move(touchEvent), completionHandler = WTF::move(completionHandler)] mutable { RefPtr webPage = WebProcess::singleton().webPage(pageID); bool handled = false; if (webPage) - handled = webPage->dispatchTouchEvent(touchEvent); + handled = webPage->dispatchTouchEvent(touchEvent.copyRef()); if (completionHandler) - completionHandler(touchEvent.type(), handled); + completionHandler(touchEvent->type(), handled); }); } -void EventDispatcher::touchEvent(PageIdentifier pageID, FrameIdentifier frameID, const WebTouchEvent& touchEvent, CompletionHandler&& completionHandler) +void EventDispatcher::touchEvent(PageIdentifier pageID, FrameIdentifier frameID, Ref&& touchEvent, CompletionHandler&& completionHandler) { RefPtr scrollingTree; { @@ -323,29 +324,29 @@ void EventDispatcher::touchEvent(PageIdentifier pageID, FrameIdentifier frameID, } if (!scrollingTree) { - dispatchTouchEventViaMainThread(pageID, touchEvent, WTF::move(completionHandler)); + dispatchTouchEventViaMainThread(pageID, WTF::move(touchEvent), WTF::move(completionHandler)); return; } auto trackingType = scrollingTree->eventTrackingTypeForTouchEvent(platform(touchEvent)); if (trackingType == TrackingType::NotTracking) { - completionHandler(touchEvent.type(), false); + completionHandler(touchEvent->type(), false); return; } if (trackingType == TrackingType::Asynchronous) { - completionHandler(touchEvent.type(), false); - dispatchTouchEventViaMainThread(pageID, touchEvent, nullptr); + completionHandler(touchEvent->type(), false); + dispatchTouchEventViaMainThread(pageID, WTF::move(touchEvent), nullptr); return; } - dispatchTouchEventViaMainThread(pageID, touchEvent, WTF::move(completionHandler)); + dispatchTouchEventViaMainThread(pageID, WTF::move(touchEvent), WTF::move(completionHandler)); } #endif void EventDispatcher::dispatchWheelEventViaMainThread(WebCore::PageIdentifier pageID, const WebWheelEvent& wheelEvent, OptionSet processingSteps, CompletionHandler&& completionHandler) { ASSERT(!RunLoop::isMain()); - RunLoop::mainSingleton().dispatch([this, protectedThis = Ref { *this }, pageID, wheelEvent, steps = processingSteps - WheelEventProcessingSteps::AsyncScrolling, completionHandler = WTF::move(completionHandler)] mutable { + RunLoop::mainSingleton().dispatch([this, protectedThis = Ref { *this }, pageID, wheelEvent = Ref { wheelEvent }, steps = processingSteps - WheelEventProcessingSteps::AsyncScrolling, completionHandler = WTF::move(completionHandler)] mutable { dispatchWheelEvent(pageID, wheelEvent, steps, WTF::move(completionHandler)); }); } diff --git a/Source/WebKit/WebProcess/WebPage/EventDispatcher.h b/Source/WebKit/WebProcess/WebPage/EventDispatcher.h index 37debc2ad825..e55e9b49ddc6 100644 --- a/Source/WebKit/WebProcess/WebPage/EventDispatcher.h +++ b/Source/WebKit/WebProcess/WebPage/EventDispatcher.h @@ -71,13 +71,13 @@ struct RemoteWebTouchEvent; #if ENABLE(IOS_TOUCH_EVENTS) struct TouchEventData { - TouchEventData(WebCore::FrameIdentifier, const WebTouchEvent&, CompletionHandler)>&&); + TouchEventData(WebCore::FrameIdentifier, Ref&&, CompletionHandler)>&&); TouchEventData(TouchEventData&&); ~TouchEventData(); TouchEventData& operator=(TouchEventData&&); WebCore::FrameIdentifier frameID; - WebTouchEvent event; + Ref event; Vector)>> completionHandlers; }; #endif @@ -119,18 +119,18 @@ class EventDispatcher final : void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override; // Message handlers - void wheelEvent(WebCore::PageIdentifier, const WebWheelEvent&, WebCore::RectEdges rubberBandableEdges, CompletionHandler&&); + void wheelEvent(WebCore::PageIdentifier, Ref&&, WebCore::RectEdges rubberBandableEdges, CompletionHandler&&); #if ENABLE(MOMENTUM_EVENT_DISPATCHER) void setScrollingAccelerationCurve(WebCore::PageIdentifier, std::optional&&); #endif #if ENABLE(IOS_TOUCH_EVENTS) - void touchEvent(WebCore::PageIdentifier, WebCore::FrameIdentifier, const WebTouchEvent&, CompletionHandler)>&&); + void touchEvent(WebCore::PageIdentifier, WebCore::FrameIdentifier, Ref&&, CompletionHandler)>&&); #elif ENABLE(COORDINATED_TOUCH_EVENTS) - void dispatchTouchEventViaMainThread(WebCore::PageIdentifier, const WebTouchEvent&, CompletionHandler&&); - void touchEvent(WebCore::PageIdentifier, WebCore::FrameIdentifier, const WebTouchEvent&, CompletionHandler&&); + void dispatchTouchEventViaMainThread(WebCore::PageIdentifier, Ref&&, CompletionHandler&&); + void touchEvent(WebCore::PageIdentifier, WebCore::FrameIdentifier, Ref&&, CompletionHandler&&); #endif #if ENABLE(MAC_GESTURE_EVENTS) - void gestureEvent(WebCore::FrameIdentifier, WebCore::PageIdentifier, const WebGestureEvent&, CompletionHandler, bool, std::optional)>&&); + void gestureEvent(WebCore::FrameIdentifier, WebCore::PageIdentifier, Ref&&, CompletionHandler, bool, std::optional)>&&); #endif // This is called on the main thread. diff --git a/Source/WebKit/WebProcess/WebPage/EventDispatcher.messages.in b/Source/WebKit/WebProcess/WebPage/EventDispatcher.messages.in index 4b04ee25c628..6185f20dfa7c 100644 --- a/Source/WebKit/WebProcess/WebPage/EventDispatcher.messages.in +++ b/Source/WebKit/WebProcess/WebPage/EventDispatcher.messages.in @@ -25,15 +25,15 @@ DispatchedTo=WebContent ] messages -> EventDispatcher { - WheelEvent(WebCore::PageIdentifier pageID, WebKit::WebWheelEvent event, WebCore::RectEdges rubberBandableEdges) -> (bool handled) AnyThread + WheelEvent(WebCore::PageIdentifier pageID, Ref event, WebCore::RectEdges rubberBandableEdges) -> (bool handled) AnyThread #if ENABLE(IOS_TOUCH_EVENTS) - TouchEvent(WebCore::PageIdentifier pageID, WebCore::FrameIdentifier frameID, WebKit::WebTouchEvent event) -> (bool handled, struct std::optional transformedEvent) MainThreadCallback + TouchEvent(WebCore::PageIdentifier pageID, WebCore::FrameIdentifier frameID, Ref event) -> (bool handled, struct std::optional transformedEvent) MainThreadCallback #endif #if ENABLE(COORDINATED_TOUCH_EVENTS) - TouchEvent(WebCore::PageIdentifier pageID, WebCore::FrameIdentifier frameID, WebKit::WebTouchEvent event) -> (enum:uint32_t WebKit::WebEventType eventType, bool handled) AnyThread + TouchEvent(WebCore::PageIdentifier pageID, WebCore::FrameIdentifier frameID, Ref event) -> (enum:uint32_t WebKit::WebEventType eventType, bool handled) AnyThread #endif #if ENABLE(MAC_GESTURE_EVENTS) - GestureEvent(WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID, WebKit::WebGestureEvent event) -> (enum:uint32_t std::optional eventType, bool handled, struct std::optional remoteUserInputEventData) MainThreadCallback + GestureEvent(WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID, Ref event) -> (enum:uint32_t std::optional eventType, bool handled, struct std::optional remoteUserInputEventData) MainThreadCallback #endif #if HAVE(DISPLAY_LINK) DisplayDidRefresh(uint32_t displayID, struct WebCore::DisplayUpdate update, bool sendToMainThread) diff --git a/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.cpp b/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.cpp index 2304d4ebe292..3c04c77d3a3b 100644 --- a/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.cpp +++ b/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.cpp @@ -75,7 +75,7 @@ bool MomentumEventDispatcher::eventShouldStartSyntheticMomentumPhase(WebCore::Pa bool MomentumEventDispatcher::handleWheelEvent(WebCore::PageIdentifier pageIdentifier, const WebWheelEvent& event, WebCore::RectEdges rubberBandableEdges) { m_lastRubberBandableEdges = rubberBandableEdges; - m_lastIncomingEvent = event; + m_lastIncomingEvent = &event; bool isMomentumEvent = event.isMomentumEvent(); @@ -169,7 +169,7 @@ void MomentumEventDispatcher::dispatchSyntheticMomentumEvent(WebWheelEvent::Phas ASSERT(m_currentGesture.active); ASSERT(m_currentGesture.initiatingEvent); - CheckedRef initiatingEvent = *m_currentGesture.initiatingEvent; + Ref initiatingEvent = *m_currentGesture.initiatingEvent; auto appKitScrollMultiplier = appKitScrollMultiplierForEvent(initiatingEvent); auto appKitAcceleratedDelta = delta * appKitScrollMultiplier; auto wheelTicks = appKitAcceleratedDelta / WebCore::Scrollbar::pixelsPerLineStep(); @@ -177,23 +177,23 @@ void MomentumEventDispatcher::dispatchSyntheticMomentumEvent(WebWheelEvent::Phas // FIXME: Ideally we would stick legitimate rawPlatformDeltas on the event, // but currently nothing will consume them, and we'd have to keep track of them separately. - WebWheelEvent syntheticEvent( - { WebEventType::Wheel, m_lastIncomingEvent->modifiers(), time }, - initiatingEvent->position(), - initiatingEvent->globalPosition(), - appKitAcceleratedDelta, - wheelTicks, - WebWheelEvent::Granularity::ScrollByPixelWheelEvent, - initiatingEvent->directionInvertedFromDevice(), - WebWheelEvent::Phase::None, - phase, - true, - initiatingEvent->scrollCount(), - delta, - time, - { }, - WebWheelEvent::MomentumEndType::Unknown, - initiatingEvent->inputSource()); + Ref syntheticEvent = WebWheelEvent::create({ WebEventType::Wheel, m_lastIncomingEvent->modifiers(), time }, { + .position = initiatingEvent->position(), + .globalPosition = initiatingEvent->globalPosition(), + .delta = appKitAcceleratedDelta, + .wheelTicks = wheelTicks, + .granularity = WebWheelEvent::Granularity::ScrollByPixelWheelEvent, + .directionInvertedFromDevice = initiatingEvent->directionInvertedFromDevice(), + .phase = WebWheelEvent::Phase::None, + .momentumPhase = phase, + .hasPreciseScrollingDeltas = true, + .scrollCount = initiatingEvent->scrollCount(), + .unacceleratedScrollingDelta = delta, + .ioHIDEventTimestamp = time, + .rawPlatformDelta = { }, + .momentumEndType = WebWheelEvent::MomentumEndType::Unknown, + .inputSource = initiatingEvent->inputSource(), + }); m_client->handleSyntheticWheelEvent(*m_currentGesture.pageIdentifier, syntheticEvent, m_lastRubberBandableEdges); @@ -214,7 +214,7 @@ void MomentumEventDispatcher::didStartMomentumPhase(WebCore::PageIdentifier page m_currentGesture.active = true; m_currentGesture.momentumCurve = event.inputSource() == WebEventInputSource::Automation ? MomentumCurve::Simple : MomentumCurve::Default; m_currentGesture.pageIdentifier = pageIdentifier; - m_currentGesture.initiatingEvent = event; + m_currentGesture.initiatingEvent = &event; m_currentGesture.currentOffset = { }; m_currentGesture.startTime = MonotonicTime::now(); m_currentGesture.displayNominalFrameRate = displayProperties->nominalFrameRate; diff --git a/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.h b/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.h index acbac0e3a420..265d9a090dc4 100644 --- a/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.h +++ b/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.h @@ -153,7 +153,7 @@ class MomentumEventDispatcher { HistoricalDeltas m_deltaHistoryY; Markable m_lastScrollTimestamp; - std::optional m_lastIncomingEvent; + RefPtr m_lastIncomingEvent; WebCore::RectEdges m_lastRubberBandableEdges; bool m_isInOverriddenPlatformMomentumGesture { false }; @@ -164,7 +164,7 @@ class MomentumEventDispatcher { Markable pageIdentifier; std::optional accelerationCurve; - std::optional initiatingEvent; + RefPtr initiatingEvent; WebCore::FloatSize currentOffset; MonotonicTime startTime; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp index 7c054752f606..d3d03ad804e8 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp @@ -4140,7 +4140,10 @@ class CurrentEvent { } private: - CheckedPtr m_previousCurrentEvent; + // Owning: the previous event is alive in an outer scope, and holding a reference is cheaper than + // a weak pointer here. g_currentEvent itself stays raw so that dispatching an event, which + // happens for every mouse move, does not touch a refcount. + RefPtr m_previousCurrentEvent; }; #if ENABLE(CONTEXT_MENUS) @@ -4181,8 +4184,9 @@ void WebPage::contextMenuForKeyEvent() } #endif -void WebPage::mouseEvent(FrameIdentifier frameID, const WebMouseEvent& mouseEvent, std::optional>&& sandboxExtensions, CompletionHandler)>&& completionHandler) +void WebPage::mouseEvent(FrameIdentifier frameID, Ref&& mouseEventRef, std::optional>&& sandboxExtensions, CompletionHandler)>&& completionHandler) { + const auto& mouseEvent = mouseEventRef.get(); SetForScope userIsInteractingChange { m_userIsInteracting, true }; m_internals->userActivity.impulse(); @@ -4307,8 +4311,9 @@ void WebPage::flushDeferredDidReceiveMouseEvent() info->completionHandler(info->handled, std::nullopt); } -void WebPage::performHitTestForMouseEvent(const WebMouseEvent& event, CompletionHandler)>&& completionHandler) +void WebPage::performHitTestForMouseEvent(Ref&& eventRef, CompletionHandler)>&& completionHandler) { + const auto& event = eventRef.get(); auto modifiers = event.modifiers(); RefPtr localMainFrame = dynamicDowncast(corePage()->mainFrame()); if (!localMainFrame || !localMainFrame->view()) @@ -4325,8 +4330,9 @@ void WebPage::performHitTestForMouseEvent(const WebMouseEvent& event, Completion completionHandler(WTF::move(hitTestResultData), modifiers); } -void WebPage::handleWheelEvent(FrameIdentifier frameID, const WebWheelEvent& event, const OptionSet& processingSteps, std::optional willStartSwipe, CompletionHandler, std::optional, bool, std::optional)>&& completionHandler) +void WebPage::handleWheelEvent(FrameIdentifier frameID, Ref&& eventRef, const OptionSet& processingSteps, std::optional willStartSwipe, CompletionHandler, std::optional, bool, std::optional)>&& completionHandler) { + const auto& event = eventRef.get(); #if ENABLE(ASYNC_SCROLLING) RefPtr remoteScrollingCoordinator = dynamicDowncast(scrollingCoordinator()); if (remoteScrollingCoordinator) @@ -4367,8 +4373,9 @@ std::pair> WebPage::wheelEv } #if PLATFORM(IOS_FAMILY) -void WebPage::dispatchWheelEventWithoutScrolling(FrameIdentifier frameID, const WebWheelEvent& wheelEvent, CompletionHandler)>&& completionHandler) +void WebPage::dispatchWheelEventWithoutScrolling(FrameIdentifier frameID, Ref&& wheelEventRef, CompletionHandler)>&& completionHandler) { + const auto& wheelEvent = wheelEventRef.get(); #if ENABLE(KINETIC_SCROLLING) RefPtr frame = WebProcess::singleton().webFrame(frameID); RefPtr localFrame = frame ? frame->coreLocalFrame() : nullptr; @@ -4383,8 +4390,9 @@ void WebPage::dispatchWheelEventWithoutScrolling(FrameIdentifier frameID, const } #endif -void WebPage::keyEvent(FrameIdentifier frameID, const WebKeyboardEvent& keyboardEvent, CompletionHandler&& completionHandler) +void WebPage::keyEvent(FrameIdentifier frameID, Ref&& keyboardEventRef, CompletionHandler&& completionHandler) { + const auto& keyboardEvent = keyboardEventRef.get(); SetForScope userIsInteractingChange { m_userIsInteracting, true }; m_internals->userActivity.impulse(); @@ -4572,8 +4580,9 @@ void WebPage::updatePotentialTapSecurityOrigin(const WebTouchEvent& touchEvent, m_potentialTapSecurityOrigin = targetDocument->securityOrigin(); } #elif ENABLE(TOUCH_EVENTS) -void WebPage::touchEvent(const WebTouchEvent& touchEvent, CompletionHandler, bool)>&& completionHandler) +void WebPage::touchEvent(Ref&& touchEventRef, CompletionHandler, bool)>&& completionHandler) { + const auto& touchEvent = touchEventRef.get(); RefPtr localMainFrame = this->localMainFrame(); if (!localMainFrame) return; @@ -4587,10 +4596,10 @@ void WebPage::touchEvent(const WebTouchEvent& touchEvent, CompletionHandler&& event) { bool result = false; - touchEvent(event, [&](std::optional, bool handled) { + touchEvent(WTF::move(event), [&](std::optional, bool handled) { result = handled; }); return result; @@ -4778,7 +4787,7 @@ void WebPage::viewWillEndLiveResize() view->willEndLiveResize(); } -void WebPage::setInitialFocus(bool forward, bool isKeyboardEventValid, const std::optional& event, CompletionHandler&& completionHandler) +void WebPage::setInitialFocus(bool forward, bool isKeyboardEventValid, RefPtr&& event, CompletionHandler&& completionHandler) { if (!m_page) return completionHandler(); @@ -4792,7 +4801,7 @@ void WebPage::setInitialFocus(bool forward, bool isKeyboardEventValid, const std protect(frame->document())->setFocusedElement(nullptr); if (isKeyboardEventValid && event && event->type() == WebEventType::KeyDown) { - PlatformKeyboardEvent platformEvent(platform(CheckedRef { *event })); + PlatformKeyboardEvent platformEvent(platform(*event)); platformEvent.disambiguateKeyDownEvent(PlatformEvent::Type::RawKeyDown); focusController->setInitialFocus(forward ? FocusDirection::Forward : FocusDirection::Backward, &KeyboardEvent::create(platformEvent, &frame->windowProxy()).get()); completionHandler(); diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h index 3d6f8b1c7a75..d0fc42c78904 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.h +++ b/Source/WebKit/WebProcess/WebPage/WebPage.h @@ -1388,7 +1388,7 @@ class WebPage final : public API::ObjectImpl, pub void readSelectionFromPasteboard(const String& pasteboardName, CompletionHandler&&); void getStringSelectionForPasteboard(CompletionHandler&&); void getDataSelectionForPasteboard(const String pasteboardType, CompletionHandler&&)>&&); - void shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent&, CompletionHandler&&); + void shouldDelayWindowOrderingEvent(Ref&&, CompletionHandler&&); bool performNonEditingBehaviorForSelector(const String&, WebCore::KeyboardEvent*); #if ENABLE(MULTI_REPRESENTATION_HEIC) @@ -1407,7 +1407,7 @@ class WebPage final : public API::ObjectImpl, pub void setCaretAnimatorType(WebCore::CaretAnimatorType); void setCaretBlinkingSuspended(bool); void attributedSubstringForCharacterRangeAsync(const EditingRange&, CompletionHandler&&); - void requestAcceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent&); + void requestAcceptsFirstMouse(int eventNumber, Ref&&); #endif #if PLATFORM(COCOA) @@ -1557,7 +1557,7 @@ class WebPage final : public API::ObjectImpl, pub void handleAlternativeTextUIResult(const String&); #endif - void handleWheelEvent(WebCore::FrameIdentifier, const WebWheelEvent&, const OptionSet&, std::optional willStartSwipe, CompletionHandler, std::optional, bool handled, std::optional)>&&); + void handleWheelEvent(WebCore::FrameIdentifier, Ref&&, const OptionSet&, std::optional willStartSwipe, CompletionHandler, std::optional, bool handled, std::optional)>&&); std::pair> wheelEvent(const WebCore::FrameIdentifier&, const WebWheelEvent&, OptionSet); void wheelEventHandlersChanged(bool); @@ -1624,7 +1624,7 @@ class WebPage final : public API::ObjectImpl, pub #if ENABLE(IOS_TOUCH_EVENTS) Expected dispatchTouchEvent(WebCore::FrameIdentifier, const WebTouchEvent&); #elif ENABLE(COORDINATED_TOUCH_EVENTS) - bool dispatchTouchEvent(const WebTouchEvent&); + bool dispatchTouchEvent(Ref&&); #endif bool shouldUseCustomContentProviderForResponse(const WebCore::ResourceResponse&); @@ -2031,7 +2031,7 @@ class WebPage final : public API::ObjectImpl, pub void didAddOrRemoveViewportConstrainedObjects(); #if PLATFORM(IOS_FAMILY) - void dispatchWheelEventWithoutScrolling(WebCore::FrameIdentifier, const WebWheelEvent&, CompletionHandler)>&&); + void dispatchWheelEventWithoutScrolling(WebCore::FrameIdentifier, Ref&&, CompletionHandler)>&&); #endif #if ENABLE(PDF_PLUGIN) @@ -2382,7 +2382,7 @@ class WebPage final : public API::ObjectImpl, pub void goToBackForwardItem(GoToBackForwardItemParameters&&); [[noreturn]] void NODELETE goToBackForwardItemWaitingForProcessLaunch(GoToBackForwardItemParameters&&, WebKit::WebPageProxyIdentifier); void tryRestoreScrollPosition(); - void setInitialFocus(bool forward, bool isKeyboardEventValid, const std::optional&, CompletionHandler&&); + void setInitialFocus(bool forward, bool isKeyboardEventValid, RefPtr&&, CompletionHandler&&); void updateIsInWindow(bool isInitialState = false); void visibilityDidChange(); void windowActivityDidChange(); @@ -2401,8 +2401,8 @@ class WebPage final : public API::ObjectImpl, pub void setNeedsFontAttributes(bool); - void mouseEvent(WebCore::FrameIdentifier, const WebMouseEvent&, std::optional>&& sandboxExtensions, CompletionHandler)>&&); - void keyEvent(WebCore::FrameIdentifier, const WebKeyboardEvent&, CompletionHandler&&); + void mouseEvent(WebCore::FrameIdentifier, Ref&&, std::optional>&& sandboxExtensions, CompletionHandler)>&&); + void keyEvent(WebCore::FrameIdentifier, Ref&&, CompletionHandler&&); void setLastKnownMousePosition(WebCore::FrameIdentifier, const WebCore::DoublePoint&, const WebCore::DoublePoint&, std::optional&& = std::nullopt); @@ -2411,7 +2411,7 @@ class WebPage final : public API::ObjectImpl, pub void didBeginTouchPoint(WebCore::FloatPoint locationInRootView); void updatePotentialTapSecurityOrigin(const WebTouchEvent&, bool wasHandled); #elif ENABLE(TOUCH_EVENTS) - void touchEvent(const WebTouchEvent&, CompletionHandler, bool)>&&); + void touchEvent(Ref&&, CompletionHandler, bool)>&&); #endif void cancelPointer(WebCore::PointerID, const WebCore::IntPoint&); @@ -2652,7 +2652,7 @@ class WebPage final : public API::ObjectImpl, pub void handleAcceptedCandidate(WebCore::TextCheckingResult); #endif - void performHitTestForMouseEvent(const WebMouseEvent&, CompletionHandler)>&&); + void performHitTestForMouseEvent(Ref&&, CompletionHandler)>&&); #if PLATFORM(COCOA) void requestActiveNowPlayingSessionInfo(CompletionHandler&&); diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in index cd260bdd1051..b03c94a9fc8c 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in +++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in @@ -26,7 +26,7 @@ DispatchedTo=WebContent ] messages -> WebPage WantsAsyncDispatchMessage { - SetInitialFocus(bool forward, bool isKeyboardEventValid, std::optional event) -> () + SetInitialFocus(bool forward, bool isKeyboardEventValid, RefPtr event) -> () SetActivityState(OptionSet activityState, WebKit::ActivityStateChangeID activityStateChangeID) -> () SetBackgroundColor(std::optional color) @@ -65,8 +65,8 @@ messages -> WebPage WantsAsyncDispatchMessage { ViewWillEndLiveResize() ExecuteEditCommandWithCallback(String name, String argument) -> () - KeyEvent(WebCore::FrameIdentifier frameID, WebKit::WebKeyboardEvent event) -> (bool handled) - MouseEvent(WebCore::FrameIdentifier frameID, WebKit::WebMouseEvent event, std::optional> sandboxExtensions) -> (bool handled, struct std::optional remoteUserInputEventData) + KeyEvent(WebCore::FrameIdentifier frameID, Ref event) -> (bool handled) + MouseEvent(WebCore::FrameIdentifier frameID, Ref event, std::optional> sandboxExtensions) -> (bool handled, struct std::optional remoteUserInputEventData) SetLastKnownMousePosition(WebCore::FrameIdentifier frameID, WebCore::DoublePoint eventPoint, WebCore::DoublePoint globalPoint, enum:uint8_t std::optional source); #if PLATFORM(COCOA) @@ -186,7 +186,7 @@ messages -> WebPage WantsAsyncDispatchMessage { DidBeginTouchPoint(WebCore::FloatPoint locationInRootView) #endif #if !ENABLE(IOS_TOUCH_EVENTS) && ENABLE(TOUCH_EVENTS) - TouchEvent(WebKit::WebTouchEvent event) -> (enum:uint32_t std::optional eventType, bool handled) + TouchEvent(Ref event) -> (enum:uint32_t std::optional eventType, bool handled) #endif CancelPointer(WebCore::PointerID pointerId, WebCore::IntPoint documentPoint) @@ -596,7 +596,7 @@ messages -> WebPage WantsAsyncDispatchMessage { InsertMultiRepresentationHEIC(std::span data, String altText) #endif - ShouldDelayWindowOrderingEvent(WebKit::WebMouseEvent event) -> (bool result) Synchronous + ShouldDelayWindowOrderingEvent(Ref event) -> (bool result) Synchronous SetTextAsync(String text) @@ -620,7 +620,7 @@ messages -> WebPage WantsAsyncDispatchMessage { #endif #if PLATFORM(MAC) AttributedSubstringForCharacterRangeAsync(struct WebKit::EditingRange range) -> (struct WebCore::AttributedString string, struct WebKit::EditingRange range) - RequestAcceptsFirstMouse(int eventNumber, WebKit::WebMouseEvent event) AllowedWhenWaitingForSyncReplyDuringUnboundedIPC + RequestAcceptsFirstMouse(int eventNumber, Ref event) AllowedWhenWaitingForSyncReplyDuringUnboundedIPC SetCaretAnimatorType(enum:uint8_t WebCore::CaretAnimatorType caretType) SetCaretBlinkingSuspended(bool blinkSuspended) #endif @@ -695,7 +695,7 @@ messages -> WebPage WantsAsyncDispatchMessage { FlushDeferredDidReceiveMouseEvent() - PerformHitTestForMouseEvent(WebKit::WebMouseEvent event) -> (struct WebKit::WebHitTestResultData hitTestResult, OptionSet modifiers) + PerformHitTestForMouseEvent(Ref event) -> (struct WebKit::WebHitTestResultData hitTestResult, OptionSet modifiers) SetUseColorAppearance(bool useDarkAppearance, bool useElevatedUserInterfaceLevel) @@ -826,10 +826,10 @@ messages -> WebPage WantsAsyncDispatchMessage { CreateTextFragmentDirectiveFromSelection() -> (URL url) GetTextFragmentRanges() -> (Vector textFragmentEditingRanges) - HandleWheelEvent(WebCore::FrameIdentifier frameID, WebKit::WebWheelEvent event, OptionSet processingSteps, std::optional willStartSwipe) -> (std::optional scrollingNodeID, enum:uint8_t std::optional gestureState, bool handled, struct std::optional remoteUserInputEventData) + HandleWheelEvent(WebCore::FrameIdentifier frameID, Ref event, OptionSet processingSteps, std::optional willStartSwipe) -> (std::optional scrollingNodeID, enum:uint8_t std::optional gestureState, bool handled, struct std::optional remoteUserInputEventData) #if PLATFORM(IOS_FAMILY) - DispatchWheelEventWithoutScrolling(WebCore::FrameIdentifier frameID, WebKit::WebWheelEvent event) -> (bool defaultPrevented, struct std::optional remoteUserInputEventData) + DispatchWheelEventWithoutScrolling(WebCore::FrameIdentifier frameID, Ref event) -> (bool defaultPrevented, struct std::optional remoteUserInputEventData) #endif LastNavigationWasAppInitiated() -> (bool wasAppBound) diff --git a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm index a3b2736ddbaa..c4b39991ece4 100644 --- a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm +++ b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm @@ -808,7 +808,7 @@ static inline FloatRect adjustExposedRectForNewScale(const FloatRect& exposedRec return [NSURLConnection canHandleRequest:nsRequest.get()]; } -void WebPage::shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent&, CompletionHandler&& completionHandler) +void WebPage::shouldDelayWindowOrderingEvent(Ref&&, CompletionHandler&& completionHandler) { notImplemented(); completionHandler(false); @@ -3926,12 +3926,15 @@ static bool selectionIsInsideFixedPositionContainer(LocalFrame& frame) } #if ENABLE(IOS_TOUCH_EVENTS) -static std::optional transformEventIfNecessary(const Expected& transformer, WebTouchEvent&& event) +static std::optional transformEventIfNecessary(const Expected& transformer, const WebTouchEvent& event) { if (transformer) return std::nullopt; - event.transformToRemoteFrameCoordinates(transformer.error()); - return RemoteWebTouchEvent { transformer.error().remoteFrameID(), WTF::move(event) }; + // A deep copy: transforming mutates the event and its coalesced/predicted children in place, and + // the event we were handed is shared with the queue and its completion handlers. + Ref transformedEvent = event.copy(); + transformedEvent->transformToRemoteFrameCoordinates(transformer.error()); + return RemoteWebTouchEvent { transformer.error().remoteFrameID(), WTF::move(transformedEvent) }; } void WebPage::dispatchAsynchronousTouchEvents(UniqueRef&& queue) @@ -3947,7 +3950,7 @@ static bool selectionIsInsideFixedPositionContainer(LocalFrame& frame) completionHandler(handled, std::nullopt); // The last handler corresponds to the event that was actually dispatched. - touchEventData.completionHandlers.last()(handled, transformEventIfNecessary(handleTouchEventResult, WTF::move(touchEventData.event))); + touchEventData.completionHandlers.last()(handled, transformEventIfNecessary(handleTouchEventResult, touchEventData.event)); } } diff --git a/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm index 9dbd15c9f8e0..a272581bfdf2 100644 --- a/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm +++ b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm @@ -594,8 +594,9 @@ static String commandNameForSelectorName(const String& selectorName) #endif } -void WebPage::shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent& event, CompletionHandler&& completionHandler) +void WebPage::shouldDelayWindowOrderingEvent(Ref&& eventRef, CompletionHandler&& completionHandler) { + const auto& event = eventRef.get(); RefPtr frame = m_page->focusController().focusedOrMainFrame(); if (!frame) return completionHandler({ }); @@ -610,8 +611,9 @@ static String commandNameForSelectorName(const String& selectorName) completionHandler(result); } -void WebPage::requestAcceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event) +void WebPage::requestAcceptsFirstMouse(int eventNumber, Ref&& eventRef) { + const auto& event = eventRef.get(); if (WebProcess::singleton().parentProcessConnection()->inSendSync()) { // In case we're already inside a sendSync message, it's possible that the page is in a // transitionary state, so any hit-testing could cause crashes so we just return early in that case. From ffbf2c925325a2780830d333468cc8d71de71b71 Mon Sep 17 00:00:00 2001 From: Claudio Saavedra Date: Fri, 28 Aug 2026 02:27:01 -0700 Subject: [PATCH 023/103] [GLIB] Unskip ipc/serialized-type-info.html https://bugs.webkit.org/show_bug.cgi?id=322614 Reviewed by Carlos Alberto Lopez Perez. The test reports two types that reach IPC on the ports using Unix domain sockets and cannot be described. UnixFileDescriptor is their IPC::Attachment, the role MachSendRight has on Darwin (Platform/IPC/Attachment.h); attachments are passed out of band by a hand-written coder and have no members, which is why MachSendRight is already allowlisted. It was also spelled two ways, and only the unqualified form is registered. GTlsCertificateFlags is a GIO C flags enum, and the generator only parses enum class, the same reason WebCore::ContextMenuAction is allowlisted. Describing it would mean mirroring GIO's flags in WebKit. Allowlist both and unskip. GTK and WPE report identical sets, so one line covers them. * LayoutTests/ipc/serialized-type-info.html: * LayoutTests/platform/glib/TestExpectations: * Source/WebKit/Shared/WebGL.serialization.in: * Source/WebKit/Shared/XR/PlatformXR.serialization.in: * Source/WebKit/Shared/glib/CoreIPCGUnixFDList.serialization.in: * Source/WebKit/Shared/glib/DMABufBufferAttributes.serialization.in: Canonical link: https://commits.webkit.org/320031@main --- LayoutTests/ipc/serialized-type-info.html | 3 +++ LayoutTests/platform/glib/TestExpectations | 1 - Source/WebKit/Shared/WebGL.serialization.in | 2 +- Source/WebKit/Shared/XR/PlatformXR.serialization.in | 4 ++-- Source/WebKit/Shared/glib/CoreIPCGUnixFDList.serialization.in | 2 +- .../Shared/glib/DMABufBufferAttributes.serialization.in | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/LayoutTests/ipc/serialized-type-info.html b/LayoutTests/ipc/serialized-type-info.html index 5aa1204e40c7..17800540109f 100644 --- a/LayoutTests/ipc/serialized-type-info.html +++ b/LayoutTests/ipc/serialized-type-info.html @@ -188,6 +188,9 @@ result.push("WKDDActionContext"); } } + } else { + result.push("UnixFileDescriptor"); + result.push("GTlsCertificateFlags"); } } return result.sort(); diff --git a/LayoutTests/platform/glib/TestExpectations b/LayoutTests/platform/glib/TestExpectations index 227a66f04cea..c53e2db4a8d5 100644 --- a/LayoutTests/platform/glib/TestExpectations +++ b/LayoutTests/platform/glib/TestExpectations @@ -5221,7 +5221,6 @@ imported/w3c/web-platform-tests/webcodecs/videoFrame-copyTo.crossOriginIsolated. imported/w3c/web-platform-tests/workers/Worker-creation-happens-in-parallel.https.html [ Skip ] imported/w3c/web-platform-tests/workers/postMessage_block.https.html [ Skip ] imported/w3c/web-platform-tests/workers/Worker-postMessage-happens-in-parallel.https.html [ Skip ] -ipc/serialized-type-info.html [ Skip ] ipc/stream-sync-reply-shared-memory.html [ Skip ] webkit.org/b/297737 ipc/send-gradient.html [ Skip ] webkit.org/b/297737 ipc/send-filter.html [ Skip ] diff --git a/Source/WebKit/Shared/WebGL.serialization.in b/Source/WebKit/Shared/WebGL.serialization.in index 7ed1862c2cb2..ecdd4f3a1e9c 100644 --- a/Source/WebKit/Shared/WebGL.serialization.in +++ b/Source/WebKit/Shared/WebGL.serialization.in @@ -106,7 +106,7 @@ using WebCore::GraphicsContextGL::ExternalSyncSource = std::tuple hardwareBuffer; #else - Vector fds; + Vector fds; Vector strides; Vector offsets; uint32_t fourcc; diff --git a/Source/WebKit/Shared/XR/PlatformXR.serialization.in b/Source/WebKit/Shared/XR/PlatformXR.serialization.in index 0084a5714222..6c087e39c3fd 100644 --- a/Source/WebKit/Shared/XR/PlatformXR.serialization.in +++ b/Source/WebKit/Shared/XR/PlatformXR.serialization.in @@ -151,7 +151,7 @@ header: bool isSharedTexture; #endif #if !PLATFORM(COCOA) - Vector fds; + Vector fds; Vector strides; Vector offsets; uint32_t fourcc; @@ -333,7 +333,7 @@ header: PlatformXR::LayerHandle handle; bool visible; Vector views; - WTF::UnixFileDescriptor fenceFD; + UnixFileDescriptor fenceFD; #if ENABLE(WEBXR_LAYERS) bool blendTextureSourceAlpha; bool forceMonoPresentation; diff --git a/Source/WebKit/Shared/glib/CoreIPCGUnixFDList.serialization.in b/Source/WebKit/Shared/glib/CoreIPCGUnixFDList.serialization.in index 4454df57860e..3523d238e418 100644 --- a/Source/WebKit/Shared/glib/CoreIPCGUnixFDList.serialization.in +++ b/Source/WebKit/Shared/glib/CoreIPCGUnixFDList.serialization.in @@ -30,7 +30,7 @@ header: "CoreIPCGUnixFDList.h" additional_forward_declaration: typedef struct _GUnixFDList GUnixFDList [GRefPtrWrapped=GUnixFDList] class WebKit::CoreIPCGUnixFDList { - Vector fileDescriptors(); + Vector fileDescriptors(); } #endif diff --git a/Source/WebKit/Shared/glib/DMABufBufferAttributes.serialization.in b/Source/WebKit/Shared/glib/DMABufBufferAttributes.serialization.in index 8e87fa7b3171..d6db1a947347 100644 --- a/Source/WebKit/Shared/glib/DMABufBufferAttributes.serialization.in +++ b/Source/WebKit/Shared/glib/DMABufBufferAttributes.serialization.in @@ -26,7 +26,7 @@ header: [RValue, CustomHeader] class WebCore::DMABufBufferAttributes { WebCore::IntSize size; WebCore::FourCC fourcc; - Vector fds; + Vector fds; Vector offsets; Vector strides; uint64_t modifier; From 00c991b3e660e26f50328266eb11bfb095f2461f Mon Sep 17 00:00:00 2001 From: Richard Robinson Date: Fri, 28 Aug 2026 02:35:20 -0700 Subject: [PATCH 024/103] [Swift in WebKit] Introduce a bridging mechanism to create `std::expected`s in Swift https://bugs.webkit.org/show_bug.cgi?id=322806 rdar://186056463 Reviewed by Adrian Taylor. Add new CxxExpected and CxxConsumingExpected Swift protocols to be able to easily interoperate with `std::expected` types. Test: Tools/TestWebKitAPI/Tests/WTF/cocoa/SwiftCxxInteropTests.swift * Source/WebKit/Shared/WTFExpected+Extras.swift: Added. (CxxExpectedBase.has_value): (CxxExpectedBase.__valueUnsafe): (CxxExpectedBase.__errorUnsafe): (CxxExpected.value): (CxxConsumingExpected.__take(_:)): (CxxConsumingExpected.consume() throws(CxxUnexpected:)): * Source/WebKit/WebKit.xcodeproj/project.pbxproj: * Tools/TestWebKitAPI/Helpers/WTFExpected+Extras.swift: Added. (CxxExpectedBase.has_value): (CxxExpectedBase.__valueUnsafe): (CxxExpectedBase.__errorUnsafe): (CxxExpected.value): (CxxConsumingExpected.__take(_:)): (CxxConsumingExpected.consume() throws(CxxUnexpected:)): * Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.cpp: (SwiftCxxInteropTestbed::sharedProbeRefCount): (SwiftCxxInteropTestbed::makeIntExpected): (SwiftCxxInteropTestbed::makeIntUnexpected): (SwiftCxxInteropTestbed::makeCopyCountingProbeExpected): (SwiftCxxInteropTestbed::makeCopyCountingProbeUnexpected): (SwiftCxxInteropTestbed::makeSharedProbeHolderExpected): (SwiftCxxInteropTestbed::makeMoveOnlyProbeExpected): (SwiftCxxInteropTestbed::makeMoveOnlyProbeUnexpected): (SwiftCxxInteropTestbed::makeSelfReferentialProbeExpected): * Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.h: (SwiftCxxInteropTestbed::takeMoveOnlyProbeValue): (SwiftCxxInteropTestbed::takeSelfReferentialProbeValue): * Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: * Tools/TestWebKitAPI/Tests/WTF/cocoa/SwiftCxxInteropTests.swift: (Cxx.__take(_:)): (SwiftCxxInteropTests.expectedExposesItsValue): (SwiftCxxInteropTests.unexpectedThrowsItsError): (SwiftCxxInteropTests.readingTheValueLeavesTheExpectedAlone): (SwiftCxxInteropTests.readingAnUnexpectedLeavesTheErrorInPlace): (SwiftCxxInteropTests.readingACopyableValueCopiesItExactlyOnce): (SwiftCxxInteropTests.theValueOutlivesTheExpectedItWasReadFrom): (SwiftCxxInteropTests.readingAnUnexpectedNeverTouchesTheValue): (SwiftCxxInteropTests.valueHoldingAManagedReferenceLeavesTheCallersCountAlone): (SwiftCxxInteropTests.noncopyableValueCanBeConsumed): (SwiftCxxInteropTests.consumingAnUnexpectedThrowsItsError): (SwiftCxxInteropTests.consumedNoncopyableValueIsRelocatedWithItsMoveConstructor): Canonical link: https://commits.webkit.org/320032@main --- Source/WebKit/CMakeLists.txt | 1 + .../Shared/WTFCompletionHandler+Extras.swift | 4 +- Source/WebKit/Shared/WTFExpected+Extras.swift | 178 +++++++++++ .../WebKit/WebKit.xcodeproj/project.pbxproj | 4 + .../Helpers/WTFCompletionHandler+Extras.swift | 4 +- .../Helpers/WTFExpected+Extras.swift | 178 +++++++++++ .../TestWTFLibrary/SwiftCxxInteropTestbed.cpp | 95 ++++++ .../TestWTFLibrary/SwiftCxxInteropTestbed.h | 90 ++++++ .../TestWebKitAPI.xcodeproj/project.pbxproj | 2 + .../WTF/cocoa/SwiftCxxInteropTests.swift | 285 ++++++++++++++++++ 10 files changed, 839 insertions(+), 2 deletions(-) create mode 100644 Source/WebKit/Shared/WTFExpected+Extras.swift create mode 100644 Tools/TestWebKitAPI/Helpers/WTFExpected+Extras.swift diff --git a/Source/WebKit/CMakeLists.txt b/Source/WebKit/CMakeLists.txt index 4ea403d62eef..49f59b7dede5 100644 --- a/Source/WebKit/CMakeLists.txt +++ b/Source/WebKit/CMakeLists.txt @@ -1465,6 +1465,7 @@ if (SWIFT_REQUIRED) ${WEBKIT_DIR}/Shared/Foundation+Extras.swift ${WEBKIT_DIR}/Shared/IPCTesterReceiver.swift ${WEBKIT_DIR}/Shared/WTFCompletionHandler+Extras.swift + ${WEBKIT_DIR}/Shared/WTFExpected+Extras.swift ${WebKit_DERIVED_SOURCES_DIR}/IPCTesterReceiverMessageReceiver.swift ) endif () diff --git a/Source/WebKit/Shared/WTFCompletionHandler+Extras.swift b/Source/WebKit/Shared/WTFCompletionHandler+Extras.swift index 571935a9327f..50a56dfcc854 100644 --- a/Source/WebKit/Shared/WTFCompletionHandler+Extras.swift +++ b/Source/WebKit/Shared/WTFCompletionHandler+Extras.swift @@ -197,7 +197,9 @@ extension CxxVoidCompletionHandler where Self: ~Copyable { /// Creates a `WTF::CompletionHandler` type from a Swift closure. /// - /// - Parameter body: The Swift closure to use. + /// - Parameters: + /// - isolation: The current isolation. + /// - body: The Swift closure to use. @safe init(isolation: isolated (any Actor)? = #isolation, _ body: @escaping () -> Void) { self.init(box: SwiftVoidClosureBox(isolation: isolation, body)) diff --git a/Source/WebKit/Shared/WTFExpected+Extras.swift b/Source/WebKit/Shared/WTFExpected+Extras.swift new file mode 100644 index 000000000000..6d3ebf7925e8 --- /dev/null +++ b/Source/WebKit/Shared/WTFExpected+Extras.swift @@ -0,0 +1,178 @@ +// Copyright (C) 2026 Apple Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +// FIXME: (rdar://164119356) Move this file into WTF. + +#if compiler(>=6.4) && !SWIFT_WEBKIT_TOOLCHAIN + +/// Represents an `unexpected` value of an `std::expected`. +struct CxxUnexpected: Error { + /// The unexpected error. + let error: Failure +} + +/// The machinery shared by ``CxxExpected`` and ``CxxConsumingExpected``. +/// +/// - Important: Do not conform to this directly. Conform to one of those two instead. +protocol CxxExpectedBase: ~Copyable { + /// The type of the expected value. + associatedtype Value: ~Copyable + + /// The type of the unexpected value. + associatedtype Failure: Sendable + + /// Checks whether the object contains an expected value. + /// - Returns: `true` if the object contains an expected value; `false` otherwise. + // swift-format-ignore: AlwaysUseLowerCamelCase + func has_value() -> Bool + + /// Returns the expected value. + /// + /// - Note: Do not call or implement this yourself. It is unsafe to do so. + // swift-format-ignore: AlwaysUseLowerCamelCase,NoLeadingUnderscores + func __valueUnsafe() -> UnsafePointer + + /// Returns the unexpected value. + /// + /// - Note: Do not call or implement this yourself. It is unsafe to do so. + // swift-format-ignore: AlwaysUseLowerCamelCase,NoLeadingUnderscores + func __errorUnsafe() -> UnsafePointer +} + +/// A protocol for concrete specializations of `std::expected` to conform to when `T` is `Copyable`. +/// +/// Conforming a specialization to this protocol allows Swift to safely access properties of the `expected`. +/// For example, this makes it possible to use a type like +/// +/// ```cpp +/// using ExpectedResult = std::expected; +/// ``` +/// +/// by creating this conformance: +/// +/// ```swift +/// extension ExpectedResult: CxxExpected {} +/// ``` +/// +/// Swift can then use the `expected` naturally: +/// +/// ```swift +/// let expectedResult: ExpectedResult = ... +/// let value = try expectedResult.value +/// ``` +/// +protocol CxxExpected: CxxExpectedBase where Value: Copyable { +} + +extension CxxExpected { + /// Returns the expected value, or throws the unexpected error if one exists. + var value: Value { + get throws(CxxUnexpected) { + // Safety properties: + // + // Non-null, initialized, aligned: both point into `self`, which is a fully constructed + // `std::expected`, so each is the address of a live object of the type it points to. + // Lifetime: `self` is borrowed for the whole of this getter, so neither pointer can dangle + // here. Neither is stored; only the copy taken below escapes. + // Aliasing: `self` still owns what both point at, so this may only read them. `.pointee` + // copies, running `Value`'s or `Failure`'s copy constructor, and leaves the `Expected` + // intact and readable again. + + guard has_value() else { + throw unsafe CxxUnexpected(error: __errorUnsafe().pointee) + } + + return unsafe __valueUnsafe().pointee + } + } +} + +/// A protocol for concrete specializations of `std::expected` for noncopyable `T`s. +/// +/// This is the same as ``CxxExpected`` except that it matches a C++ signature like: +/// +/// ```cpp +/// using ExpectedResult = std::expected; +/// ``` +/// +/// Conformers to this protocol must explicitly implement the `__take(_:)` requirement like so: +/// +/// ```swift +/// extension ExpectedResult: CxxConsumingExpected { +/// static func __take(_ expected: consuming Self) -> Value { +/// takeValue(consuming: expected) +/// } +/// } +/// ``` +/// +/// where `takeValue` is a C++ free function implemented exactly as +/// +/// ```cpp +/// inline ExpectedResult::value_type takeValue(ExpectedResult&& expected) +/// { +/// // Must be non-trivially destructible so that Swift runs the move constructor rather than +/// // relocating the value byte-wise, which would leave a value holding a pointer into itself +/// // dangling. +/// static_assert(!std::is_trivially_destructible_v); +/// // Swift copies rather than consumes a copyable `Expected`, so the value would be moved out of +/// // that copy and the caller's `Expected` left untouched. +/// static_assert(!std::is_copy_constructible_v); +/// return WTF::move(*expected); +/// } +/// ``` +/// +/// Swift imports that rvalue reference parameter as `consuming`, and destroys the moved-from `expected` once the call +/// returns. +/// +/// - Important: Do not implement the protocol requirements yourself. It is unsafe if you do so. +protocol CxxConsumingExpected: CxxExpectedBase, ~Copyable { + /// Consumes the `expected`, moving its value out. + /// + /// - Parameter expected: The expected to take. It is guaranteed to have a value. + /// - Returns: The moved value of the expected. + /// - Note: Do not call this yourself. + // swift-format-ignore: AlwaysUseLowerCamelCase,NoLeadingUnderscores + static func __take(_ expected: consuming Self) -> Value +} + +extension CxxConsumingExpected where Self: ~Copyable, Value: ~Copyable { + /// Returns the expected value, or throws the unexpected error if one exists. + /// + /// - Returns: The consumed value of the expected, if one exists. + /// - Throws: The unexpected error, if one exists. + consuming func consume() throws(CxxUnexpected) -> Value { + // Safety properties: + // + // As in `CxxExpected.value`, with one difference: `self` is consumed here rather than borrowed. + // `__errorUnsafe()` only borrows it, and `__take(_:)` is what finally takes it, so `self` is + // still live when the error is read out below. + + guard has_value() else { + throw unsafe CxxUnexpected(error: __errorUnsafe().pointee) + } + + return Self.__take(self) + } +} + +#endif // compiler(>=6.4) && !SWIFT_WEBKIT_TOOLCHAIN diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj index bf5ebaa603d1..2edb6ab06842 100644 --- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj +++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj @@ -142,6 +142,7 @@ 029D6BB32C407AA30068CF99 /* JSWebExtensionAPISidebarAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 029D6BAD2C407AA30068CF99 /* JSWebExtensionAPISidebarAction.h */; }; 029D6BB42C407AA30068CF99 /* JSWebExtensionAPISidePanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 029D6BAF2C407AA30068CF99 /* JSWebExtensionAPISidePanel.h */; }; 0701789E23BE9CFC005F0FAA /* RemoteMediaPlayerMIMETypeCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0701789B23BAE261005F0FAA /* RemoteMediaPlayerMIMETypeCache.cpp */; }; + 0708B49430413DF90014BF6C /* WTFExpected+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708B49330413DF90014BF6C /* WTFExpected+Extras.swift */; }; 071467782DFE84E500F77867 /* WebPage+Transferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071467772DFE84E500F77867 /* WebPage+Transferable.swift */; }; 07152D072F2F037D00B56C0E /* WKTextSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07152D062F2F037D00B56C0E /* WKTextSelectionController.swift */; }; 07152D092F2F038A00B56C0E /* WKTextSelectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = 07152D082F2F038A00B56C0E /* WKTextSelectionController.h */; }; @@ -3438,6 +3439,7 @@ 0701789B23BAE261005F0FAA /* RemoteMediaPlayerMIMETypeCache.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RemoteMediaPlayerMIMETypeCache.cpp; sourceTree = ""; }; 0701789C23BAE262005F0FAA /* RemoteMediaPlayerMIMETypeCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemoteMediaPlayerMIMETypeCache.h; sourceTree = ""; }; 070259BE2522841C00153405 /* UserMediaPermissionRequestManagerProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UserMediaPermissionRequestManagerProxy.mm; sourceTree = ""; }; + 0708B49330413DF90014BF6C /* WTFExpected+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WTFExpected+Extras.swift"; sourceTree = ""; }; 071467772DFE84E500F77867 /* WebPage+Transferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebPage+Transferable.swift"; sourceTree = ""; }; 07152D062F2F037D00B56C0E /* WKTextSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKTextSelectionController.swift; sourceTree = ""; }; 07152D082F2F038A00B56C0E /* WKTextSelectionController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKTextSelectionController.h; sourceTree = ""; }; @@ -10734,6 +10736,7 @@ E0E1E2E3E4E5E6E700000003 /* WriteWebArchiveToPasteBoardResult.serialization.in */, 5CA6A48B28F6621800F92B6E /* WTFArgumentCoders.serialization.in */, 077DD8E6303AE8A2000AA8FC /* WTFCompletionHandler+Extras.swift */, + 0708B49330413DF90014BF6C /* WTFExpected+Extras.swift */, ); path = Shared; sourceTree = ""; @@ -22686,6 +22689,7 @@ 075369492DC589E1006446F8 /* WKWebView+TextExtraction.swift in Sources */, 079A4DA32D72CDB400CA387F /* WKWebViewConfiguration+Extras.swift in Sources */, 077DD8E7303AE8A2000AA8FC /* WTFCompletionHandler+Extras.swift in Sources */, + 0708B49430413DF90014BF6C /* WTFExpected+Extras.swift in Sources */, C14D306924B79BE000480387 /* XPCEndpoint.mm in Sources */, C14D306A24B79BE400480387 /* XPCEndpointClient.mm in Sources */, ); diff --git a/Tools/TestWebKitAPI/Helpers/WTFCompletionHandler+Extras.swift b/Tools/TestWebKitAPI/Helpers/WTFCompletionHandler+Extras.swift index 81b3d836feb6..35cae88f2cd5 100644 --- a/Tools/TestWebKitAPI/Helpers/WTFCompletionHandler+Extras.swift +++ b/Tools/TestWebKitAPI/Helpers/WTFCompletionHandler+Extras.swift @@ -156,7 +156,9 @@ extension CxxCompletionHandler where Self: ~Copyable { /// Creates a `WTF::CompletionHandler` type from a Swift closure. /// - /// - Parameter body: The Swift closure to use. + /// - Parameters: + /// - isolation: The current isolation. + /// - body: The Swift closure to use. @safe public init(isolation: isolated (any Actor)? = #isolation, _ body: @escaping (Argument) -> Void) { self.init(box: SwiftCopyingClosureBox(isolation: isolation, body)) diff --git a/Tools/TestWebKitAPI/Helpers/WTFExpected+Extras.swift b/Tools/TestWebKitAPI/Helpers/WTFExpected+Extras.swift new file mode 100644 index 000000000000..5f6bf31fea36 --- /dev/null +++ b/Tools/TestWebKitAPI/Helpers/WTFExpected+Extras.swift @@ -0,0 +1,178 @@ +// Copyright (C) 2026 Apple Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +// FIXME: (rdar://164119356) Move this file into WTF. + +#if compiler(>=6.4) && !SWIFT_WEBKIT_TOOLCHAIN + +/// Represents an `unexpected` value of an `std::expected`. +public struct CxxUnexpected: Error { + /// The unexpected error. + public let error: Failure +} + +/// The machinery shared by ``CxxExpected`` and ``CxxConsumingExpected``. +/// +/// - Important: Do not conform to this directly. Conform to one of those two instead. +public protocol CxxExpectedBase: ~Copyable { + /// The type of the expected value. + associatedtype Value: ~Copyable + + /// The type of the unexpected value. + associatedtype Failure: Sendable + + /// Checks whether the object contains an expected value. + /// - Returns: `true` if the object contains an expected value; `false` otherwise. + // swift-format-ignore: AlwaysUseLowerCamelCase + func has_value() -> Bool + + /// Returns the expected value. + /// + /// - Note: Do not call or implement this yourself. It is unsafe to do so. + // swift-format-ignore: AlwaysUseLowerCamelCase,NoLeadingUnderscores + func __valueUnsafe() -> UnsafePointer + + /// Returns the unexpected value. + /// + /// - Note: Do not call or implement this yourself. It is unsafe to do so. + // swift-format-ignore: AlwaysUseLowerCamelCase,NoLeadingUnderscores + func __errorUnsafe() -> UnsafePointer +} + +/// A protocol for concrete specializations of `std::expected` to conform to when `T` is `Copyable`. +/// +/// Conforming a specialization to this protocol allows Swift to safely access properties of the `expected`. +/// For example, this makes it possible to use a type like +/// +/// ```cpp +/// using ExpectedResult = std::expected; +/// ``` +/// +/// by creating this conformance: +/// +/// ```swift +/// extension ExpectedResult: CxxExpected {} +/// ``` +/// +/// Swift can then use the `expected` naturally: +/// +/// ```swift +/// let expectedResult: ExpectedResult = ... +/// let value = try expectedResult.value +/// ``` +/// +public protocol CxxExpected: CxxExpectedBase where Value: Copyable { +} + +extension CxxExpected { + /// Returns the expected value, or throws the unexpected error if one exists. + public var value: Value { + get throws(CxxUnexpected) { + // Safety properties: + // + // Non-null, initialized, aligned: both point into `self`, which is a fully constructed + // `std::expected`, so each is the address of a live object of the type it points to. + // Lifetime: `self` is borrowed for the whole of this getter, so neither pointer can dangle + // here. Neither is stored; only the copy taken below escapes. + // Aliasing: `self` still owns what both point at, so this may only read them. `.pointee` + // copies, running `Value`'s or `Failure`'s copy constructor, and leaves the `Expected` + // intact and readable again. + + guard has_value() else { + throw unsafe CxxUnexpected(error: __errorUnsafe().pointee) + } + + return unsafe __valueUnsafe().pointee + } + } +} + +/// A protocol for concrete specializations of `std::expected` for noncopyable `T`s. +/// +/// This is the same as ``CxxExpected`` except that it matches a C++ signature like: +/// +/// ```cpp +/// using ExpectedResult = std::expected; +/// ``` +/// +/// Conformers to this protocol must explicitly implement the `__take(_:)` requirement like so: +/// +/// ```swift +/// extension ExpectedResult: CxxConsumingExpected { +/// static func __take(_ expected: consuming Self) -> Value { +/// takeValue(consuming: expected) +/// } +/// } +/// ``` +/// +/// where `takeValue` is a C++ free function implemented exactly as +/// +/// ```cpp +/// inline ExpectedResult::value_type takeValue(ExpectedResult&& expected) +/// { +/// // Must be non-trivially destructible so that Swift runs the move constructor rather than +/// // relocating the value byte-wise, which would leave a value holding a pointer into itself +/// // dangling. +/// static_assert(!std::is_trivially_destructible_v); +/// // Swift copies rather than consumes a copyable `Expected`, so the value would be moved out of +/// // that copy and the caller's `Expected` left untouched. +/// static_assert(!std::is_copy_constructible_v); +/// return WTF::move(*expected); +/// } +/// ``` +/// +/// Swift imports that rvalue reference parameter as `consuming`, and destroys the moved-from `expected` once the call +/// returns. +/// +/// - Important: Do not implement the protocol requirements yourself. It is unsafe if you do so. +public protocol CxxConsumingExpected: CxxExpectedBase, ~Copyable { + /// Consumes the `expected`, moving its value out. + /// + /// - Parameter expected: The expected to take. It is guaranteed to have a value. + /// - Returns: The moved value of the expected. + /// - Note: Do not call this yourself. + // swift-format-ignore: AlwaysUseLowerCamelCase,NoLeadingUnderscores + static func __take(_ expected: consuming Self) -> Value +} + +extension CxxConsumingExpected where Self: ~Copyable, Value: ~Copyable { + /// Returns the expected value, or throws the unexpected error if one exists. + /// + /// - Returns: The consumed value of the expected, if one exists. + /// - Throws: The unexpected error, if one exists. + public consuming func consume() throws(CxxUnexpected) -> Value { + // Safety properties: + // + // As in `CxxExpected.value`, with one difference: `self` is consumed here rather than borrowed. + // `__errorUnsafe()` only borrows it, and `__take(_:)` is what finally takes it, so `self` is + // still live when the error is read out below. + + guard has_value() else { + throw unsafe CxxUnexpected(error: __errorUnsafe().pointee) + } + + return Self.__take(self) + } +} + +#endif // compiler(>=6.4) && !SWIFT_WEBKIT_TOOLCHAIN diff --git a/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.cpp b/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.cpp index b17836642a34..c02587eb4d2a 100644 --- a/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.cpp +++ b/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.cpp @@ -119,6 +119,34 @@ void resetCopyCountingProbeCounts() copyCountingProbeCopies() = 0; } +static int& liveCountingProbeErrors() +{ + static int count = 0; + return count; +} + +CountingProbeError::CountingProbeError(ProbeError value) + : m_value(value) +{ + ++liveCountingProbeErrors(); +} + +CountingProbeError::CountingProbeError(const CountingProbeError& other) + : m_value(other.m_value) +{ + ++liveCountingProbeErrors(); +} + +CountingProbeError::~CountingProbeError() +{ + --liveCountingProbeErrors(); +} + +int liveCountingProbeErrorCount() +{ + return liveCountingProbeErrors(); +} + int callIntBoolFunction(bool argument, IntBoolFunction&& function) { return function(argument); @@ -261,6 +289,11 @@ int sharedProbeDerefCalls() return sharedProbeDerefs(); } +int sharedProbeRefCount() +{ + return sharedProbe().refCount(); +} + void resetSharedProbe() { sharedProbeRefs() = 0; @@ -293,4 +326,66 @@ void callSelfReferentialProbeCompletionHandler(int argument, SelfReferentialProb completionHandler(SelfReferentialProbe { argument }); } +IntExpected makeIntExpected(int value) +{ + return IntExpected { value }; +} + +IntExpected makeIntUnexpected(ProbeError error) +{ + return IntExpected { makeUnexpected(error) }; +} + +CopyCountingProbeExpected makeCopyCountingProbeExpected(int value) +{ + // In place, so that the probe the `Expected` holds is the only one this constructed: a test counting + // copies is measuring what Swift did, not what building the `Expected` did. + return CopyCountingProbeExpected { std::in_place, value }; +} + +CopyCountingProbeExpected makeCopyCountingProbeUnexpected(ProbeError error) +{ + return CopyCountingProbeExpected { makeUnexpected(error) }; +} + +SharedProbeHolderExpected makeSharedProbeHolderExpected() +{ + return SharedProbeHolderExpected { SharedProbeHolder { &sharedProbe() } }; +} + +SharedProbeHolderExpected makeSharedProbeHolderUnexpected(ProbeError error) +{ + return SharedProbeHolderExpected { makeUnexpected(error) }; +} + +MoveOnlyProbeExpected makeMoveOnlyProbeExpected(int value) +{ + return MoveOnlyProbeExpected { std::in_place, value }; +} + +MoveOnlyProbeExpected makeMoveOnlyProbeUnexpected(ProbeError error) +{ + return MoveOnlyProbeExpected { makeUnexpected(error) }; +} + +SelfReferentialProbeExpected makeSelfReferentialProbeExpected(int value) +{ + return SelfReferentialProbeExpected { std::in_place, value }; +} + +SelfReferentialProbeExpected makeSelfReferentialProbeUnexpected(ProbeError error) +{ + return SelfReferentialProbeExpected { makeUnexpected(error) }; +} + +CountedErrorExpected makeCountedErrorExpected(int value) +{ + return CountedErrorExpected { std::in_place, value }; +} + +CountedErrorExpected makeCountedErrorUnexpected(ProbeError error) +{ + return CountedErrorExpected { makeUnexpected(CountingProbeError { error }) }; +} + }; diff --git a/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.h b/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.h index edd6fdc0c64f..977575ad4582 100644 --- a/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.h +++ b/Tools/TestWebKitAPI/TestWTFLibrary/SwiftCxxInteropTestbed.h @@ -28,6 +28,7 @@ #ifdef __cplusplus #import +#import #import #import @@ -106,6 +107,28 @@ class SelfReferentialProbe { int* m_self; }; +// The unexpected half of every `Expected` below. A scoped enum so that a test can tell one failure from +// another, and numbered from one so that a zeroed-out read does not look like a valid error. +enum class ProbeError : uint8_t { + TooSmall = 1, + TooLarge = 2, +}; + +// An error that counts its own lifetime. An `Expected` holding an error holds no value, so the value-side +// counters cannot say anything about the failure path; this is what proves that path destroys the +// `Expected` it consumed, exactly once, rather than leaking or double-destroying it. +class SWIFT_UNCHECKED_SENDABLE CountingProbeError { +public: + explicit CountingProbeError(ProbeError); + CountingProbeError(const CountingProbeError&); + ~CountingProbeError(); + + ProbeError value() const { return m_value; } + +private: + ProbeError m_value; +}; + // MARK: Using declarations using IntBoolFunction = WTF::Function; @@ -118,6 +141,13 @@ using CopyCountingProbeCompletionHandler = WTF::CompletionHandler; using SelfReferentialProbeCompletionHandler = WTF::CompletionHandler; +using IntExpected = Expected; +using CopyCountingProbeExpected = Expected; +using SharedProbeHolderExpected = Expected; +using MoveOnlyProbeExpected = Expected; +using SelfReferentialProbeExpected = Expected; +using CountedErrorExpected = Expected; + // MARK: Function declarations int callIntBoolFunction(bool, IntBoolFunction&&); @@ -158,10 +188,70 @@ int callSharedProbeHolderCompletionHandler(SharedProbeHolderCompletionHandler&&) // How many times the bridge caused a retain or a release. These must match. int sharedProbeRefCalls(); int sharedProbeDerefCalls(); + +// The shared probe's current reference count. One means nothing but the caller's own reference is left. +int sharedProbeRefCount(); + void resetSharedProbe(); void callSelfReferentialProbeCompletionHandler(int, SelfReferentialProbeCompletionHandler&&); +// MARK: Expected + +IntExpected makeIntExpected(int); +IntExpected makeIntUnexpected(ProbeError); + +// A copyable value has to be copied out of the `Expected`, so exactly one copy constructor call should be +// observable per read. +CopyCountingProbeExpected makeCopyCountingProbeExpected(int); +CopyCountingProbeExpected makeCopyCountingProbeUnexpected(ProbeError); + +// Holds a borrowed reference to the shared probe, like callSharedProbeHolderCompletionHandler() passes. +SharedProbeHolderExpected makeSharedProbeHolderExpected(); +SharedProbeHolderExpected makeSharedProbeHolderUnexpected(ProbeError); + +MoveOnlyProbeExpected makeMoveOnlyProbeExpected(int); +MoveOnlyProbeExpected makeMoveOnlyProbeUnexpected(ProbeError); + +SelfReferentialProbeExpected makeSelfReferentialProbeExpected(int); +SelfReferentialProbeExpected makeSelfReferentialProbeUnexpected(ProbeError); + +CountedErrorExpected makeCountedErrorExpected(int); +CountedErrorExpected makeCountedErrorUnexpected(ProbeError); + +// Zero once every error the bridge constructed has been destroyed. See CountingProbeError. +int liveCountingProbeErrorCount(); + +// The `__take(_:)` witnesses for the `CxxConsumingExpected` conformances. Swift imports the rvalue +// reference as the `consuming` the protocol hands over, and destroys the moved-from `Expected` once the +// call returns. +// +// The three checks each of these repeats are what make a witness safe to write; none of them is +// diagnosed at the call site. See CxxConsumingExpected's documentation. + +inline MoveOnlyProbeExpected::value_type takeMoveOnlyProbeValue(MoveOnlyProbeExpected&& expected) +{ + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + return WTF::move(*expected); +} + +inline SelfReferentialProbeExpected::value_type takeSelfReferentialProbeValue(SelfReferentialProbeExpected&& expected) +{ + // See takeMoveOnlyProbeValue(). + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + return WTF::move(*expected); +} + +inline CountedErrorExpected::value_type takeCountedErrorValue(CountedErrorExpected&& expected) +{ + // See takeMoveOnlyProbeValue(). + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + return WTF::move(*expected); +} + } inline void refSharedProbe(SwiftCxxInteropTestbed::SharedProbe* WTF_NONNULL probe) diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj index 41f2dff7e062..a83fd09fa86d 100644 --- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj +++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj @@ -652,6 +652,7 @@ mac/VirtualGamepad.mm, mac/WebKitAgnosticTest.mm, "WTFCompletionHandler+Extras.swift", + "WTFExpected+Extras.swift", ); target = 7CCE7E8B1A41144E00447C4C /* TestWebKitAPILibrary */; }; @@ -661,6 +662,7 @@ cocoa/UtilitiesCocoa.mm, Counters.cpp, "WTFCompletionHandler+Extras.swift", + "WTFExpected+Extras.swift", ); target = 7C83DE951D0A590C00FEBCF3 /* TestWTFLibrary */; }; diff --git a/Tools/TestWebKitAPI/Tests/WTF/cocoa/SwiftCxxInteropTests.swift b/Tools/TestWebKitAPI/Tests/WTF/cocoa/SwiftCxxInteropTests.swift index 8f6173fd33e4..7471c9ddb52c 100644 --- a/Tools/TestWebKitAPI/Tests/WTF/cocoa/SwiftCxxInteropTests.swift +++ b/Tools/TestWebKitAPI/Tests/WTF/cocoa/SwiftCxxInteropTests.swift @@ -65,6 +65,42 @@ extension Cxx.SelfReferentialProbeCompletionHandler: @unsafe TestWTFLibrary.CxxC public typealias Argument = SwiftCxxInteropTestbed.SelfReferentialProbe } +// The simplest `Expected` shape, and the one ``CxxExpected``'s documentation uses as its example. +extension Cxx.IntExpected: TestWTFLibrary.CxxExpected {} + +// Copyable but not trivially copyable: reading this value out has to run its copy constructor. +extension Cxx.CopyCountingProbeExpected: TestWTFLibrary.CxxExpected {} + +// Trivially copyable in C++, but Swift imports the `probe` field as a managed reference, so reading the +// value has to leave the C++ side's ownership alone. +extension Cxx.SharedProbeHolderExpected: TestWTFLibrary.CxxExpected {} + +// `MoveOnlyProbe` is move-only in C++, so the `Expected` holding one is noncopyable too and its value can +// only be moved out, which is what `CxxConsumingExpected` is for. +extension Cxx.MoveOnlyProbeExpected: TestWTFLibrary.CxxConsumingExpected { + // swift-format-ignore: AllPublicDeclarationsHaveDocumentation, AlwaysUseLowerCamelCase, NoLeadingUnderscores + public static func __take(_ expected: consuming Self) -> Value { + Cxx.takeMoveOnlyProbeValue(consuming: expected) + } +} + +// Noncopyable, and notices if the value is relocated byte-wise on its way out. +extension Cxx.SelfReferentialProbeExpected: @unsafe TestWTFLibrary.CxxConsumingExpected { + // swift-format-ignore: AllPublicDeclarationsHaveDocumentation, AlwaysUseLowerCamelCase, NoLeadingUnderscores + public static func __take(_ expected: consuming Self) -> Value { + unsafe Cxx.takeSelfReferentialProbeValue(consuming: expected) + } +} + +// An error that counts its own lifetime, so that the failure path -- which holds no value, and so moves +// none of the counters above -- can be checked at all. +extension Cxx.CountedErrorExpected: TestWTFLibrary.CxxConsumingExpected { + // swift-format-ignore: AllPublicDeclarationsHaveDocumentation, AlwaysUseLowerCamelCase, NoLeadingUnderscores + public static func __take(_ expected: consuming Self) -> Value { + Cxx.takeCountedErrorValue(consuming: expected) + } +} + // MARK: - Helpers /// Observed via a weak reference to prove the closure context was released. @@ -305,6 +341,255 @@ struct SwiftCxxInteropTests { "a noncopyable argument must be relocated with its move constructor, not by copying its bytes" ) } + + // MARK: Expected + + @Test + func expectedExposesItsValue() async throws { + let value = try Cxx.makeIntExpected(3).value + + #expect(value == 3) + } + + @Test + func unexpectedThrowsItsError() async throws { + let unexpected = Cxx.makeIntUnexpected(.TooLarge) + + #expect(!unexpected.has_value()) + + let thrown = try #require(throws: CxxUnexpected.self) { + _ = try unexpected.value + } + #expect(thrown.error == .TooLarge) + } + + @Test + func readingTheValueLeavesTheExpectedAlone() async throws { + let expected = Cxx.makeIntExpected(7) + + // `value` borrows rather than takes, so nothing here should be a one-shot read. + let first = try expected.value + let second = try expected.value + + #expect(first == 7) + #expect(second == 7) + #expect(expected.has_value()) + } + + @Test + func readingAnUnexpectedLeavesTheErrorInPlace() async throws { + let unexpected = Cxx.makeIntUnexpected(.TooSmall) + + // See readingTheValueLeavesTheExpectedAlone(): the failure path has to be repeatable too. + for _ in 0..<2 { + let thrown = try #require(throws: CxxUnexpected.self) { + _ = try unexpected.value + } + #expect(thrown.error == .TooSmall) + } + } + + // MARK: Copyable, non-trivially-copyable values + + @Test + func readingACopyableValueCopiesItExactlyOnce() async throws { + Cxx.resetCopyCountingProbeCounts() + + // Sampled rather than assumed to be zero. There is no way to reset a count of live objects, so a + // probe stranded by an earlier test would otherwise fail this one and point the reader here + // rather than at the leak. + let baseline = Cxx.liveCopyCountingProbeCount() + + do { + let expected = Cxx.makeCopyCountingProbeExpected(11) + let probe = try expected.value + + // Sampled before the last use of either probe below, so neither can have been released early + // by the time these are read. + let copies = Cxx.copyCountingProbeCopyCount() + let live = Cxx.liveCopyCountingProbeCount() - baseline + + #expect(probe.value() == 11, "Swift should see the value C++ stored") + #expect(expected.has_value(), "reading the value should not have emptied the expected") + #expect(copies == 1, "exactly one copy should be made for Swift") + #expect(live == 2, "the expected should still hold its own probe") + } + + #expect(Cxx.liveCopyCountingProbeCount() == baseline, "every probe should be destroyed") + } + + @Test + func theValueOutlivesTheExpectedItWasReadFrom() async throws { + Cxx.resetCopyCountingProbeCounts() + + // See readingACopyableValueCopiesItExactlyOnce(). + let baseline = Cxx.liveCopyCountingProbeCount() + + let probe: Cxx.CopyCountingProbe + do { + let expected = Cxx.makeCopyCountingProbeExpected(12) + probe = try expected.value + } + + let live = Cxx.liveCopyCountingProbeCount() - baseline + + // The expected -- and the probe it held -- are gone, so a `value` that handed Swift a pointer into + // the expected rather than a copy would be reading freed storage here. + #expect(probe.value() == 12) + #expect(live == 1, "only Swift's copy should still be alive") + } + + @Test + func readingAnUnexpectedNeverTouchesTheValue() async throws { + Cxx.resetCopyCountingProbeCounts() + + // See readingACopyableValueCopiesItExactlyOnce(). + let baseline = Cxx.liveCopyCountingProbeCount() + + do { + let unexpected = Cxx.makeCopyCountingProbeUnexpected(.TooSmall) + + let thrown = try #require(throws: CxxUnexpected.self) { + _ = try unexpected.value + } + + #expect(thrown.error == .TooSmall) + #expect(Cxx.copyCountingProbeCopyCount() == 0, "an expected holding an error has no value to copy") + } + + #expect(Cxx.liveCopyCountingProbeCount() == baseline, "every probe should be destroyed") + } + + // MARK: Values holding a managed reference + + @Test + func valueHoldingAManagedReferenceLeavesTheCallersCountAlone() async throws { + Cxx.resetSharedProbe() + + do { + let holder = try Cxx.makeSharedProbeHolderExpected().value + + // See readingACopyableValueCopiesItExactlyOnce(): sampled before the last use of `holder`, so + // its reference is certainly still held here. + let countWhileHeld = Cxx.sharedProbeRefCount() + withExtendedLifetime(holder) {} + + #expect(countWhileHeld == 2, "Swift's copy of the value should hold a reference of its own") + } + + #expect( + Cxx.sharedProbeRefCalls() == Cxx.sharedProbeDerefCalls(), + "reading the value must balance every retain and release it causes" + ) + #expect(Cxx.sharedProbeRefCount() == 1, "the C++ side's own reference must survive the read") + } + + @Test + func readingAnUnexpectedNeverRetainsTheManagedReference() async throws { + Cxx.resetSharedProbe() + + do { + let unexpected = Cxx.makeSharedProbeHolderUnexpected(.TooSmall) + + let thrown = try #require(throws: CxxUnexpected.self) { + _ = try unexpected.value + } + + // A bridge that loaded the value union before checking has_value() would have retained the + // shared probe by now. + #expect(thrown.error == .TooSmall) + #expect(Cxx.sharedProbeRefCount() == 1, "an expected holding an error has no value to retain") + } + + #expect( + Cxx.sharedProbeRefCalls() == Cxx.sharedProbeDerefCalls(), + "reading the error must balance every retain and release it causes" + ) + #expect(Cxx.sharedProbeRefCount() == 1, "the C++ side's own reference must survive the read") + } + + // MARK: Noncopyable values + + @Test + func noncopyableValueCanBeConsumed() async throws { + // See readingACopyableValueCopiesItExactlyOnce(). + let baseline = Cxx.liveMoveOnlyProbeCount() + + do { + let probe = try Cxx.makeMoveOnlyProbeExpected(5).consume() + + let live = Cxx.liveMoveOnlyProbeCount() - baseline + + // A moved-from probe reports -1, so this also proves Swift was handed the destination of the + // move rather than its source. + #expect(probe.value() == 5) + #expect(live == 1, "only the consumed value should still be alive") + } + + #expect( + Cxx.liveMoveOnlyProbeCount() == baseline, + "the consumed value should be destroyed exactly once" + ) + } + + @Test + func consumingAnUnexpectedThrowsItsError() async throws { + let thrown = try #require(throws: CxxUnexpected.self) { + _ = try Cxx.makeMoveOnlyProbeUnexpected(.TooLarge).consume() + } + + #expect(thrown.error == .TooLarge) + } + + @Test + func consumingAnUnsafeUnexpectedThrowsItsError() async throws { + // consumingAnUnexpectedThrowsItsError() covers the failure path for a value Swift imports as + // safe; this covers it for one Swift imports as `@unsafe`. + let thrown = try #require(throws: CxxUnexpected.self) { + _ = try unsafe Cxx.makeSelfReferentialProbeUnexpected(.TooSmall).consume() + } + + #expect(thrown.error == .TooSmall) + } + + @Test + func consumingAnUnexpectedDestroysTheExpectedItConsumed() async throws { + // An expected holding an error holds no value, so none of the value-side counters can see what + // the failure path did with the expected it was handed. An error that counts its own lifetime + // can: this is what proves `consume()` neither leaks nor double-destroys on the way out. + let baseline = Cxx.liveCountingProbeErrorCount() + + do { + let thrown = try #require(throws: CxxUnexpected.self) { + _ = try Cxx.makeCountedErrorUnexpected(.TooLarge).consume() + } + + #expect(thrown.error.value() == .TooLarge) + } + + #expect( + Cxx.liveCountingProbeErrorCount() == baseline, + "the failure path should destroy the expected it consumed, exactly once" + ) + } + + @Test + func consumedNoncopyableValueIsRelocatedWithItsMoveConstructor() async throws { + // The same property noncopyableArgumentIsRelocatedWithItsMoveConstructor() pins, on the way out of + // an expected rather than into a completion handler: taking the value has to run the C++ move + // constructor, or the probe's pointer into itself is left aimed at storage C++ has abandoned. + // takeSelfReferentialProbeValue()'s static assertion is what makes Swift do that. + let probe = try unsafe Cxx.makeSelfReferentialProbeExpected(7).consume() + + let interiorPointerIsValid = unsafe probe.interiorPointerIsValid() + let valueThroughInteriorPointer = unsafe probe.valueThroughInteriorPointer() + + #expect( + interiorPointerIsValid, + "a noncopyable value must be relocated with its move constructor, not by copying its bytes" + ) + #expect(valueThroughInteriorPointer == 7) + } } #endif // ENABLE_CXX_INTEROP && compiler(>=6.4) && !SWIFT_WEBKIT_TOOLCHAIN From 969d785301481e0141ae6aba801611296ffe26ec Mon Sep 17 00:00:00 2001 From: Kate Lee Date: Fri, 28 Aug 2026 02:54:25 -0700 Subject: [PATCH 025/103] [GTK][WPE] Gardening of tests - 2026-08-28 https://bugs.webkit.org/show_bug.cgi?id=322823 Unreviewed gardening. * LayoutTests/platform/glib/TestExpectations: * LayoutTests/platform/gtk/TestExpectations: * LayoutTests/platform/wpe/TestExpectations: Canonical link: https://commits.webkit.org/320033@main --- LayoutTests/platform/glib/TestExpectations | 6 +++++- LayoutTests/platform/gtk/TestExpectations | 5 +++++ LayoutTests/platform/wpe/TestExpectations | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/LayoutTests/platform/glib/TestExpectations b/LayoutTests/platform/glib/TestExpectations index c53e2db4a8d5..54befdcd53c7 100644 --- a/LayoutTests/platform/glib/TestExpectations +++ b/LayoutTests/platform/glib/TestExpectations @@ -2846,7 +2846,7 @@ imported/w3c/web-platform-tests/webrtc-extensions/transfer-datachannel-service-w imported/w3c/web-platform-tests/webrtc/simulcast/setParameters-maxFramerate.https.html [ Skip ] # Timeout imported/w3c/web-platform-tests/webrtc/simulcast/vp9-scalability-mode.https.html [ Skip ] # Timeout webrtc/getDisplayMedia-odd-size.html [ Skip ] # Timeout -webrtc/video-rotation.html [ Failure ] +webkit.org/b/322821 webrtc/video-rotation.html [ Failure Pass Timeout ] webrtc/video-maxBitrate-vp8.html [ Skip ] # Timeout webrtc/video-maxBitrate.html [ Skip ] # Timeout @@ -5396,6 +5396,10 @@ webkit.org/b/322641 [ Debug ] imported/w3c/web-platform-tests/web-animations/tim webkit.org/b/322642 [ Debug ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-unconnected-outputs.https.window.html [ Pass Failure ] webkit.org/b/322646 [ Debug ] media/track/track-readiness-state.html [ Pass Failure ] +webkit.org/b/322814 imported/w3c/web-platform-tests/mediacapture-record/MediaRecorder-error.html [ Crash Failure Pass ] +webkit.org/b/322815 imported/w3c/web-platform-tests/mediacapture-record/MediaRecorder-pause-resume.html [ Pass Timeout ] +webkit.org/b/322816 imported/w3c/web-platform-tests/resource-timing/initiator-type/style.html [ Failure Pass ] + # End: Common failures between GTK and WPE. #//////////////////////////////////////////////////////////////////////////////////////// diff --git a/LayoutTests/platform/gtk/TestExpectations b/LayoutTests/platform/gtk/TestExpectations index 257a1131d315..6ea6ec7aab59 100644 --- a/LayoutTests/platform/gtk/TestExpectations +++ b/LayoutTests/platform/gtk/TestExpectations @@ -1323,3 +1323,8 @@ webkit.org/b/322648 [ Debug ] navigation-api/navigation-api-rate-limit-history-a webkit.org/b/322650 [ Debug ] webrtc/video-h264.html [ Pass Failure ] webkit.org/b/322651 [ Debug ] webrtc/video-remote-mute.html [ Pass Failure ] webkit.org/b/322652 [ Debug ] workers/btoa-oom.html [ Pass Timeout ] +webkit.org/b/322811 [ Debug ] fast/mediastream/applyConstraints-with-takePhoto.html [ Pass Timeout ] +webkit.org/b/322818 [ Debug ] imported/w3c/web-platform-tests/wasm/core/simd/simd_f32x4_cmp.wast.js.html [ Pass Timeout ] +webkit.org/b/322819 [ Debug ] imported/w3c/web-platform-tests/wasm/core/simd/simd_f64x2_cmp.wast.js.html [ Pass Timeout ] +webkit.org/b/319070 [ Debug ] imported/w3c/web-platform-tests/wasm/core/simd/simd_f32x4_rounding.wast.js.html [ Pass Timeout ] +webkit.org/b/322820 [ Release ] media/video-seek-past-end-paused.html [ Failure Pass ] diff --git a/LayoutTests/platform/wpe/TestExpectations b/LayoutTests/platform/wpe/TestExpectations index 8bea42cc0bee..07feb9107ba0 100644 --- a/LayoutTests/platform/wpe/TestExpectations +++ b/LayoutTests/platform/wpe/TestExpectations @@ -1326,3 +1326,4 @@ webkit.org/b/322026 [ arm64 ] imported/w3c/web-platform-tests/wasm/core/f64_cmp. webkit.org/b/321789 animations/no-style-recalc-during-accelerated-animation.html [ Failure Pass ] webkit.org/b/322637 [ Debug ] imported/w3c/web-platform-tests/fullscreen/model/remove-last.html [ Pass Failure ] webkit.org/b/322639 [ Debug ] imported/w3c/web-platform-tests/html/semantics/popovers/popover-remove-attribute-during-focusing-steps.html [ Pass Failure ] +webkit.org/b/322813 [ arm64 ] imported/w3c/web-platform-tests/css/css-grid/subgrid/subgrid-baseline-018.html [ Crash Pass Timeout ] From 029da7d3d074a75a24c2413e98b183a8bbfcfb62 Mon Sep 17 00:00:00 2001 From: Antoine Quint Date: Fri, 28 Aug 2026 03:25:38 -0700 Subject: [PATCH 026/103] Page freezes with CSS transition duration calc(infinity * 1s) https://bugs.webkit.org/show_bug.cgi?id=321274 rdar://184322389 Reviewed by Sam Weinig. Ensure we don't enter an infinite loop under `UnitBezier::solveCurveX()` by enforcing a minimal value for `epsilon` larger than 0. Test: imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation.html * LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation.html: Added. * Source/WebCore/platform/graphics/UnitBezier.h: (WebCore::UnitBezier::solveCurveX): Canonical link: https://commits.webkit.org/320034@main --- ...duration-infinite-cancelation-expected.txt | 3 ++ ...nsition-duration-infinite-cancelation.html | 46 +++++++++++++++++++ Source/WebCore/platform/graphics/UnitBezier.h | 4 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation-expected.txt new file mode 100644 index 000000000000..8df21383d165 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation-expected.txt @@ -0,0 +1,3 @@ + +PASS Canceling a transition with transition-duration set to infinite value + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation.html new file mode 100644 index 000000000000..0db5049ff0b9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/transition-duration-infinite-cancelation.html @@ -0,0 +1,46 @@ + + + +Canceling a transition with transition-duration set to infinite value + + + + + + + + +
+ + + diff --git a/Source/WebCore/platform/graphics/UnitBezier.h b/Source/WebCore/platform/graphics/UnitBezier.h index 38aae7750b90..0e7f01bdd8b1 100644 --- a/Source/WebCore/platform/graphics/UnitBezier.h +++ b/Source/WebCore/platform/graphics/UnitBezier.h @@ -37,6 +37,7 @@ namespace WebCore { #define CUBIC_BEZIER_SPLINE_SAMPLES 11 static constexpr double kBezierEpsilon = 1e-7; + static constexpr double kBisectionEpsilon = 1e-10; static constexpr int kMaxNewtonIterations = 4; UnitBezier(double p1x, double p1y, double p2x, double p2y) @@ -144,9 +145,10 @@ namespace WebCore { return t2; // Fall back to the bisection method for reliability. + double bisectionEpsilon = std::max(kBisectionEpsilon, epsilon); while (t0 < t1) { x2 = sampleCurveX(t2); - if (std::abs(x2 - x) < epsilon) + if (std::abs(x2 - x) < bisectionEpsilon) return t2; if (x > x2) t0 = t2; From 129c7bc8c6be1a8736bf74a4d8090bf1c9c725cd Mon Sep 17 00:00:00 2001 From: Diego Pino Garcia Date: Fri, 28 Aug 2026 05:31:43 -0700 Subject: [PATCH 027/103] [GLib] Add monado packages to system dependencies list https://bugs.webkit.org/show_bug.cgi?id=317644 Reviewed by Adrian Perez de Castro. This patch adds the packages 'monado-cli', 'monado-service' and 'libopenxr1-monado' to the APT dependencies list. All these packages are available on the currently supported distributions, except for Debian 12. Thus, the packages are guarded by 'aptIfExists'. * Tools/glib/dependencies/apt: Canonical link: https://commits.webkit.org/320035@main --- Tools/glib/dependencies/apt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tools/glib/dependencies/apt b/Tools/glib/dependencies/apt index cbb017f11b57..4530f890d7db 100755 --- a/Tools/glib/dependencies/apt +++ b/Tools/glib/dependencies/apt @@ -98,6 +98,9 @@ PACKAGES=( hyphen-en-us gdb libcgi-pm-perl + $(aptIfExists monado-cli) + $(aptIfExists monado-service) + $(aptIfExists libopenxr1-monado) psmisc $(aptIfExists python3-legacy-cgi) pulseaudio-utils From 0c7b760ec659dbc17ea0653bc48893872490c879 Mon Sep 17 00:00:00 2001 From: Adrian Taylor Date: Fri, 28 Aug 2026 05:47:41 -0700 Subject: [PATCH 028/103] Pass correct linker prefix on PlayStation platform https://bugs.webkit.org/show_bug.cgi?id=322829 Reviewed by Ross Kirsling. The LINKER: prefix is built into CMake and arranges for tools to pass arguments through compiler drivers directly to linkers. In the case of the PlayStation port, this LINKER: prefix is being substituted for -Xlinker, but it appears that -Wl is more likely correct. Canonical link: https://commits.webkit.org/320036@main --- Source/cmake/OptionsPlayStation.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/cmake/OptionsPlayStation.cmake b/Source/cmake/OptionsPlayStation.cmake index 648ed61142b9..f55771f999f8 100644 --- a/Source/cmake/OptionsPlayStation.cmake +++ b/Source/cmake/OptionsPlayStation.cmake @@ -15,6 +15,12 @@ add_definitions(-DSCE_LIBC_DISABLE_CPP14_HEADER_WARNING= -DSCE_LIBC_DISABLE_CPP1 # bug-224462 WEBKIT_PREPEND_GLOBAL_COMPILER_FLAGS(-Wno-dll-attribute-on-redeclaration) +# bug-322829 +set(CMAKE_C_LINKER_WRAPPER_FLAG "-Wl,") +set(CMAKE_C_LINKER_WRAPPER_FLAG_SEP ",") +set(CMAKE_CXX_LINKER_WRAPPER_FLAG "-Wl,") +set(CMAKE_CXX_LINKER_WRAPPER_FLAG_SEP ",") + # Set the standard libary version WEBKIT_PREPEND_GLOBAL_COMPILER_FLAGS(-sce-stdlib=v2) From c67740bfb13bb1719cd1be0a480c4970aedb0189 Mon Sep 17 00:00:00 2001 From: Fady Farag Date: Fri, 28 Aug 2026 06:31:33 -0700 Subject: [PATCH 029/103] Fix typo in `WGSL::Types::Primitive` and `WGSL::nameForPrimitiveKind` https://bugs.webkit.org/show_bug.cgi?id=322769 rdar://186031593 Reviewed by Mike Wyrzykowski. Both strings appear in WGSL diagnostics. * Source/WebGPU/WGSL/GlobalVariableRewriter.cpp: (WGSL::nameForPrimitiveKind): * Source/WebGPU/WGSL/Types.h: Canonical link: https://commits.webkit.org/320037@main --- Source/WebGPU/WGSL/GlobalVariableRewriter.cpp | 2 +- Source/WebGPU/WGSL/Types.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp b/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp index 01e678c3fe95..20144245fb91 100644 --- a/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp +++ b/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp @@ -2008,7 +2008,7 @@ static ASCIILiteral nameForPrimitiveKind(Types::Primitive::Kind primitiveKind) case Types::Primitive::Sampler: return "sampler"_s; case Types::Primitive::SamplerComparison: - return "sampler_comparion"_s; + return "sampler_comparison"_s; case Types::Primitive::TextureExternal: return "texture_external"_s; case Types::Primitive::AccessMode: diff --git a/Source/WebGPU/WGSL/Types.h b/Source/WebGPU/WGSL/Types.h index dfd21d428751..cdd343d0d66f 100644 --- a/Source/WebGPU/WGSL/Types.h +++ b/Source/WebGPU/WGSL/Types.h @@ -72,7 +72,7 @@ namespace Types { f(Void, "void") \ f(Bool, "bool") \ f(Sampler, "sampler") \ - f(SamplerComparison, "sampler_comparion") \ + f(SamplerComparison, "sampler_comparison") \ f(TextureExternal, "texture_external") \ f(AccessMode, "access_mode") \ f(TexelFormat, "texel_format") \ From 2668e2d7adfe98f5191c1929948f40214bd4da46 Mon Sep 17 00:00:00 2001 From: Justin Michaud Date: Fri, 28 Aug 2026 06:51:37 -0700 Subject: [PATCH 030/103] [non-cocoa][fuzz] OOB write in APNG ICC conversion https://bugs.webkit.org/show_bug.cgi?id=321751 Reviewed by Adrian Perez de Castro. The reporter fixed this upstream, just add a test. Canonical link: https://commits.webkit.org/320038@main --- .../apng-icc-frame-right-half-expected.html | 6 ++++++ .../images/apng-icc-frame-right-half.html | 19 ++++++++++++++++++ .../apng-icc-frame-right-half-reference.png | Bin 0 -> 391 bytes .../resources/apng-icc-frame-right-half.png | Bin 0 -> 513 bytes 4 files changed, 25 insertions(+) create mode 100644 LayoutTests/fast/images/apng-icc-frame-right-half-expected.html create mode 100644 LayoutTests/fast/images/apng-icc-frame-right-half.html create mode 100644 LayoutTests/fast/images/resources/apng-icc-frame-right-half-reference.png create mode 100644 LayoutTests/fast/images/resources/apng-icc-frame-right-half.png diff --git a/LayoutTests/fast/images/apng-icc-frame-right-half-expected.html b/LayoutTests/fast/images/apng-icc-frame-right-half-expected.html new file mode 100644 index 000000000000..0af273762072 --- /dev/null +++ b/LayoutTests/fast/images/apng-icc-frame-right-half-expected.html @@ -0,0 +1,6 @@ + + + diff --git a/LayoutTests/fast/images/apng-icc-frame-right-half.html b/LayoutTests/fast/images/apng-icc-frame-right-half.html new file mode 100644 index 000000000000..fcffb7d65d5e --- /dev/null +++ b/LayoutTests/fast/images/apng-icc-frame-right-half.html @@ -0,0 +1,19 @@ + + + + + diff --git a/LayoutTests/fast/images/resources/apng-icc-frame-right-half-reference.png b/LayoutTests/fast/images/resources/apng-icc-frame-right-half-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec71fb1b99e73dee2ec2c57e42fc26b029222a3 GIT binary patch literal 391 zcmV;20eJq2P)YPf`k)97r8>0P zlJr^&O>VfkKDrBCJGqMBZ%}9FZi0h2_!A1EvqOXyoCMEwIK#&WV(Qf@^@Vka6Kh(H zifng$x%@_fB?|1Z9Yk5`HJgV3iB1&1_QKp} zVP;jF$%7=)Ce$cKIsn>|4X~$%iKO%2taI3W=-G1{wFMs zXDz&jhY@VO&RX#X)~?|GDLOOfKhr`0-&530#AbFxC;$Ke9Z5t%RCt_YP;f{XMZ*~V lZ(tY@#4zAKA4S6w0RU<7N$7L>nKu9c002ovPDHLkV1mjuqz(W8 literal 0 HcmV?d00001 diff --git a/LayoutTests/fast/images/resources/apng-icc-frame-right-half.png b/LayoutTests/fast/images/resources/apng-icc-frame-right-half.png new file mode 100644 index 0000000000000000000000000000000000000000..0fb539f9d3adb786552d9342eacd797ed28daa3f GIT binary patch literal 513 zcmeAS@N?(olHy`uVBq!ia0vp^CxDoVg9%7Z@??=_U|>|ubaoC1$jC3rFV4s>Q1Eni zRwyXSPs_|nWniedwYJaN`)~k*+xgHnJ63oI9JekBUeep0{c8aS-%_hBHz$3G;`Ytk zvD0oBcdYf!t_S%6>Cf|;nk{|^b1mBJ+#)uI<$Q_7;p6N{udZr+Z7uB(&W`Hs$|2x!~N|U0WDJ=kodPFV9*Ut+o5o>sJ9M z7bm8L&N4nBwQDFyy{3i5NpuYrpm-1 z`bkG1`r70LKt(X47=dh%bs%~H5QA7iqq_d*t_QM((o!5lfHV`(xF7cq7$|Ts_^!9U T8G6lg8c3<9tDnm{r-UW|_|CYV literal 0 HcmV?d00001 From 18e08875bad5ffc5ba0e934ecff74dab03c6bd81 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Fri, 28 Aug 2026 07:25:11 -0700 Subject: [PATCH 031/103] Creating or destroying a ThreadSafeWeakPtr should be considered no-delete https://bugs.webkit.org/show_bug.cgi?id=322826 Reviewed by Chris Dumez. Add NODELETE with SUPPRESS_NODELETE on functions in ThreadSafeWeakPtr so that creating a destroying a ThreadSafeWeakPtr is considered no-delete compliant. * Source/WTF/wtf/ThreadSafeWeakPtr.h: (WTF::ThreadSafeWeakPtrControlBlock::weakDeref): (WTF::ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr::controlBlock const): Canonical link: https://commits.webkit.org/320039@main --- Source/WTF/wtf/ThreadSafeWeakPtr.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/WTF/wtf/ThreadSafeWeakPtr.h b/Source/WTF/wtf/ThreadSafeWeakPtr.h index a97c97c2fdb3..b31935fd0348 100644 --- a/Source/WTF/wtf/ThreadSafeWeakPtr.h +++ b/Source/WTF/wtf/ThreadSafeWeakPtr.h @@ -52,7 +52,8 @@ class ThreadSafeWeakPtrControlBlock { return this; } - void weakDeref() + // Deleting ThreadSafeWeakPtrControlBlock does not delete a user object. + SUPPRESS_NODELETE void NODELETE weakDeref() { bool shouldDeleteControlBlock { false }; { @@ -394,7 +395,8 @@ class ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr { protected: ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr() = default; - ThreadSafeWeakPtrControlBlock& controlBlock() const + // Creating & destroying ThreadSafeWeakPtrControlBlock does not delete an user object. + SUPPRESS_NODELETE ThreadSafeWeakPtrControlBlock& NODELETE controlBlock() const { // If we ever decided there was a lot of contention here we could have some lock bits in m_bits but // that seems unlikely since this is a one-way street. Once we add a controlBlock we don't go back From d729ce684003576e8ba3bceb657927685fac982f Mon Sep 17 00:00:00 2001 From: Antoine Quint Date: Fri, 28 Aug 2026 07:30:48 -0700 Subject: [PATCH 032/103] [scroll-animations] make timeline names loosely-matched https://bugs.webkit.org/show_bug.cgi?id=322013 rdar://185206745 Reviewed by Anne van Kesteren. Now that we have the `animation-timeline`, `scroll-timeline-name` and `view-timeline-name` CSS properties track their style scope (see 320026@main) we can use `Style::resolveTreeScopedReference()` to correctly match style-originated timelines. * LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/scroll-timeline-name-shadow-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/view-timeline-name-shadow-expected.txt: * Source/WebCore/animation/StyleOriginatedTimelinesController.cpp: (WebCore::timelineIsInScopeForTarget): (WebCore::StyleOriginatedTimelinesController::determineTimelineForElement): (WebCore::StyleOriginatedTimelinesController::attachAnimation): * Source/WebCore/animation/StyleOriginatedTimelinesController.h: Canonical link: https://commits.webkit.org/320040@main --- .../scroll-timeline-name-shadow-expected.txt | 4 ++-- .../view-timeline-name-shadow-expected.txt | 4 ++-- .../StyleOriginatedTimelinesController.cpp | 21 +++++++++++++++++-- .../StyleOriginatedTimelinesController.h | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/scroll-timeline-name-shadow-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/scroll-timeline-name-shadow-expected.txt index e886fe3918ca..8a64dfdd16f9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/scroll-timeline-name-shadow-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/scroll-timeline-name-shadow-expected.txt @@ -1,6 +1,6 @@ -FAIL Outer animation can not see scroll timeline defined by :host assert_equals: expected "x" but got "y" -FAIL Outer animation can not see scroll timeline defined by ::slotted assert_equals: expected "x" but got "y" +PASS Outer animation can not see scroll timeline defined by :host +PASS Outer animation can not see scroll timeline defined by ::slotted PASS Inner animation can see scroll timeline defined by ::part PASS Animation inside shadow DOM can see the scroll timeline defined by the ancestor DOM diff --git a/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/view-timeline-name-shadow-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/view-timeline-name-shadow-expected.txt index 99f3418cc8f8..b7de7d0d38a0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/view-timeline-name-shadow-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/scroll-animations/css/view-timeline-name-shadow-expected.txt @@ -1,6 +1,6 @@ -FAIL Outer animation can not see view timeline defined by :host assert_equals: expected "x" but got "y" -FAIL Outer animation can not see view timeline defined by ::slotted assert_equals: expected "x" but got "y" +PASS Outer animation can not see view timeline defined by :host +PASS Outer animation can not see view timeline defined by ::slotted PASS Inner animation can see view timeline defined by ::part PASS Animation inside shadow DOM can see the view timeline defined by the ancestor DOM diff --git a/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp b/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp index 4ec6b9052f3d..374d866961db 100644 --- a/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp +++ b/Source/WebCore/animation/StyleOriginatedTimelinesController.cpp @@ -40,6 +40,7 @@ #include "ScrollTimeline.h" #include "Settings.h" #include "StyleableInlines.h" +#include "StyleScope.h" #include "ViewTimeline.h" #include "WebAnimation.h" #include "WebAnimationTypes.h" @@ -125,7 +126,20 @@ ScrollTimeline* StyleOriginatedTimelinesController::determineTreeOrder(const Vec return nullptr; } -ScrollTimeline* StyleOriginatedTimelinesController::determineTimelineForElement(const Vector>& timelines, const Styleable& styleable, const Element* timelineScopeElement) +static bool timelineIsInScopeForTarget(const Ref& timeline, Element& targetElement, Style::ScopeOrdinal animationTimelineNameScopeOrdinal) +{ + if (!targetElement.isConnected()) + return false; + RefPtr timelineOriginatingElement { originatingElement(timeline).element() }; + ASSERT(timelineOriginatingElement); + CheckedPtr scrollTimelineNameStyleScope = Style::Scope::forOrdinal(*timelineOriginatingElement, timeline->name().scopeOrdinal); + ASSERT(scrollTimelineNameStyleScope); + return Style::resolveTreeScopedReference(targetElement, { timeline->name().name, animationTimelineNameScopeOrdinal }, [&](const Style::Scope& scope, const Style::ScopedName&) { + return scrollTimelineNameStyleScope == &scope; + }); +} + +ScrollTimeline* StyleOriginatedTimelinesController::determineTimelineForElement(const Vector>& timelines, const Styleable& styleable, Style::ScopeOrdinal targetTimelineScopeOrdinal, const Element* timelineScopeElement) { // https://drafts.csswg.org/scroll-animations-1/#timeline-scoping // A named scroll progress timeline or view progress timeline is referenceable by: @@ -139,6 +153,9 @@ ScrollTimeline* StyleOriginatedTimelinesController::determineTimelineForElement( auto styleableForTimeline = originatingStyleableIncludingTimelineScope(timeline).styleable(); if (!styleableForTimeline) continue; + Ref targetElement { styleable.element }; + if (!timelineIsInScopeForTarget(timeline, targetElement.get(), targetTimelineScopeOrdinal)) + continue; Ref protectedElementForTimeline { styleableForTimeline->element }; if (&styleableForTimeline->element == &styleable.element || styleable.element.isComposedTreeDescendantOf(protectedElementForTimeline.get())) matchedTimelines.append(timeline); @@ -385,7 +402,7 @@ void StyleOriginatedTimelinesController::attachAnimation(CSSAnimation& animation protectedAnimation->setTimeline(nullptr); } else { auto& timelines = it->value; - RefPtr timeline = determineTimelineForElement(timelines, *target, relevantTimelineScopeElement.get()); + RefPtr timeline = determineTimelineForElement(timelines, *target, timelineName->scopeOrdinal, relevantTimelineScopeElement.get()); LOG_WITH_STREAM(Animations, stream << "StyleOriginatedTimelinesController::attachAnimation: " << timelineName->name << " styleable: " << *target << " attaching to timeline of element: " << originatingElement(*timeline)); // A deferred inactive timeline means there was a conflict with multiple timelines existing within // a parent element with a "timeline-scope" property. In that case, we must reconsider timeline attachment diff --git a/Source/WebCore/animation/StyleOriginatedTimelinesController.h b/Source/WebCore/animation/StyleOriginatedTimelinesController.h index 5a9fe0f8419d..1e3d26a153c5 100644 --- a/Source/WebCore/animation/StyleOriginatedTimelinesController.h +++ b/Source/WebCore/animation/StyleOriginatedTimelinesController.h @@ -84,7 +84,7 @@ class StyleOriginatedTimelinesController final : public CanMakeCheckedPtr>&, const Styleable&, const Element*); + ScrollTimeline* determineTimelineForElement(const Vector>&, const Styleable&, Style::ScopeOrdinal targetTimelineScopeOrdinal, const Element*); ScrollTimeline* determineTreeOrder(const Vector>&, const Styleable&, const Element*); ScrollTimeline& inactiveNamedTimeline(const AtomString&); From 27441f0bb3d1de2fa8f5bcb1fcf3aa10a78bed8b Mon Sep 17 00:00:00 2001 From: Karl Rackler Date: Fri, 28 Aug 2026 07:47:22 -0700 Subject: [PATCH 033/103] [Gardening]: (REGRESSION(318498@main): [Tahoe] 11 imported/w3c/web-platform-tests/encrypted-media/drm-mp4 tests are a constant CRASH) https://bugs.webkit.org/show_bug.cgi?id=322840 rdar://186082244 Unreviewed test gardening. Add test expectation. * LayoutTests/platform/mac-wk2/TestExpectations: Canonical link: https://commits.webkit.org/320041@main --- LayoutTests/platform/mac-wk2/TestExpectations | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/LayoutTests/platform/mac-wk2/TestExpectations b/LayoutTests/platform/mac-wk2/TestExpectations index 486a1a6d5e0c..027767521231 100644 --- a/LayoutTests/platform/mac-wk2/TestExpectations +++ b/LayoutTests/platform/mac-wk2/TestExpectations @@ -2471,4 +2471,17 @@ webkit.org/b/322051 [ Release ] media/picture-in-picture/picture-in-picture-inte [ Tahoe+ ] http/tests/storageAccess/request-and-grant-access-cross-origin-sandboxed-iframe-from-prevalent-domain-with-user-interaction-and-access-from-right-frame.https.html [ Failure ] [ Tahoe+ ] http/tests/storageAccess/request-and-grant-access-cross-origin-sandboxed-iframe-from-prevalent-domain-with-user-interaction-but-access-from-wrong-frame.https.html [ Failure ] -webkit.org/b/321323 [ Tahoe+ ] http/tests/websocket/tests/hybi/inspector/before-load.html [ Failure ] \ No newline at end of file +webkit.org/b/321323 [ Tahoe+ ] http/tests/websocket/tests/hybi/inspector/before-load.html [ Failure ] + +# webkit.org/b/322840 REGRESSION(318498@main): [Tahoe] 11 imported/w3c/web-platform-tests/encrypted-media/drm-mp4 tests are a constant CRASH +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary-encrypted-clear-sources.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary-playduration.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary-setMediaKeys-after-update.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary-setMediaKeys-immediately.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary-setMediaKeys-onencrypted.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary-two-videos.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary-waitingforkey.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-setmediakeys-again-after-playback.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-setmediakeys-again-after-resetting-src.https.html [ Failure Crash ] +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-waiting-for-a-key.https.html [ Failure Crash ] \ No newline at end of file From 05b6f71120e1843b3f7461a3033edc833e3f2d30 Mon Sep 17 00:00:00 2001 From: Ben Nham Date: Fri, 28 Aug 2026 08:04:04 -0700 Subject: [PATCH 034/103] Create dispatch queues with autorelease pools https://bugs.webkit.org/show_bug.cgi?id=290004 rdar://147361301 Reviewed by Per Arne Vollan. For legacy reasons, dispatch queues do not drain an autorelease pool for each work item. Instead, root autoreleased objects will accumulate in a last-resort pool which drains when the worker thread backing the queue exits. To fix this, use the `WITH_AUTORELEASE_POOL` attribute when creating dispatch queues so each work item is executed with its own autorelease pool. Also add a style rule to enforce this in the future. * Source/JavaScriptCore/API/tests/Regress141275.mm: (-[JSTEvaluator init]): * Source/JavaScriptCore/inspector/remote/cocoa/RemoteInspectorCocoa.mm: (Inspector::RemoteInspector::RemoteInspector): * Source/JavaScriptCore/jsc.cpp: (jscmain): * Source/WTF/wtf/cocoa/WorkQueueCocoa.cpp: (WTF::WorkQueueBase::platformInitialize): * Source/WTF/wtf/darwin/DispatchExtras.h: (WTF::serialQueueWithAutoreleasePoolAttrSingleton): (WTF::concurrentQueueWithAutoreleasePoolAttrSingleton): * Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.mm: (WebCore::NetworkExtensionContentFilter::initialize): * Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm: (WebCore::globalLoaderDelegateQueue): * Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm: (WebCore::globalOutputDelegateQueue): * Source/WebCore/platform/graphics/cocoa/PeriodicSharedTimer.mm: (WebCore::PeriodicSharedTimer::PeriodicSharedTimer): * Source/WebCore/platform/mac/PowerObserverMac.cpp: (WebCore::PowerObserver::PowerObserver): * Source/WebCore/platform/mediastream/cocoa/AVVideoCaptureSource.mm: (WebCore::globaVideoCaptureSerialQueue): * Source/WebCore/platform/mediastream/cocoa/ScreenCaptureKitCaptureSource.mm: (WebCore::ScreenCaptureKitCaptureSource::captureQueue): * Source/WebKit/NetworkProcess/webrtc/NetworkRTCTCPSocketCocoa.mm: (WebKit::tcpSocketQueueSingleton): * Source/WebKit/NetworkProcess/webrtc/NetworkRTCUDPSocketCocoa.mm: (WebKit::udpSocketQueueSingleton): * Source/WebKit/UIProcess/Cocoa/WKScreenTimeConfigurationObserver.mm: (screenTimeUpdateQueueSingleton): * Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm: (WebKit::WebProcessPool::setMediaAccessibilityPreferences): * Source/WebKit/UIProcess/XR/ios/WKARPresentationSession.mm: (-[_WKTransientGestureRecognizer initWithSession:]): * Source/WebKit/UIProcess/XR/xros/WKXRTrackingManager.mm: (-[WKXRTrackingManager initWithHandTrackingEnabled:layerRenderer:controllerManager:]): * Source/WebKit/UIProcess/ios/WKWebGeolocationPolicyDeciderIOS.mm: (-[WKWebGeolocationPolicyDecider init]): * Source/WebKit/UIProcess/mac/ServicesController.mm: (WebKit::ServicesController::ServicesController): * Source/WebKit/webpushd/_WKMockUserNotificationCenter.mm: (-[_WKMockUserNotificationCenter initWithBundleIdentifierInternal:]): * Source/WebKitLegacy/mac/WebView/WebPreferences.mm: (WebPreferencesPrivate::WebPreferencesPrivate): * Tools/Scripts/webkitpy/style/checkers/cpp.py: (check_language): (CppChecker): * Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py: (CppStyleTest): (WebKitStyleTest.test_auto_with_adopt): (WebKitStyleTest.test_wtf_os_object_ptr): * Tools/TestWebKitAPI/Helpers/mac/VirtualGamepad.mm: (TestWebKitAPI::VirtualGamepad::VirtualGamepad): * Tools/TestWebKitAPI/Tests/WTF/darwin/OSObjectPtr.cpp: (TestWebKitAPI::TEST(OS_OBJECT_PTR_TEST_NAME, AdoptOSObject)): (TestWebKitAPI::TEST(OS_OBJECT_PTR_TEST_NAME, RetainRelease)): (TestWebKitAPI::TEST(OS_OBJECT_PTR_TEST_NAME, LeakRef)): Canonical link: https://commits.webkit.org/320042@main --- .../JavaScriptCore/API/tests/Regress141275.mm | 2 +- .../remote/cocoa/RemoteInspectorCocoa.mm | 2 +- Source/JavaScriptCore/jsc.cpp | 3 +- Source/WTF/wtf/cocoa/WorkQueueCocoa.cpp | 2 +- Source/WTF/wtf/darwin/DispatchExtras.h | 12 +++++ .../cocoa/NetworkExtensionContentFilter.mm | 3 +- .../MediaPlayerPrivateAVFoundationObjC.mm | 2 +- .../avfoundation/objc/QueuedVideoOutput.mm | 3 +- .../graphics/cocoa/PeriodicSharedTimer.mm | 3 +- .../WebCore/platform/mac/PowerObserverMac.cpp | 3 +- .../mediastream/cocoa/AVVideoCaptureSource.mm | 2 +- .../cocoa/ScreenCaptureKitCaptureSource.mm | 3 +- .../webrtc/NetworkRTCTCPSocketCocoa.mm | 3 +- .../webrtc/NetworkRTCUDPSocketCocoa.mm | 3 +- .../WKScreenTimeConfigurationObserver.mm | 2 +- .../UIProcess/Cocoa/WebProcessPoolCocoa.mm | 2 +- .../XR/ios/WKARPresentationSession.mm | 3 +- .../UIProcess/XR/xros/WKXRTrackingManager.mm | 3 +- .../ios/WKWebGeolocationPolicyDeciderIOS.mm | 2 +- .../UIProcess/mac/ServicesController.mm | 2 +- .../webpushd/_WKMockUserNotificationCenter.mm | 3 +- .../mac/WebView/WebPreferences.mm | 3 +- Tools/Scripts/webkitpy/style/checkers/cpp.py | 13 +++++ .../webkitpy/style/checkers/cpp_unittest.py | 47 ++++++++++++++----- .../Helpers/mac/VirtualGamepad.mm | 2 +- .../Tests/WTF/darwin/OSObjectPtr.cpp | 7 +-- 26 files changed, 99 insertions(+), 36 deletions(-) diff --git a/Source/JavaScriptCore/API/tests/Regress141275.mm b/Source/JavaScriptCore/API/tests/Regress141275.mm index fd13edca35d9..ece269daa477 100644 --- a/Source/JavaScriptCore/API/tests/Regress141275.mm +++ b/Source/JavaScriptCore/API/tests/Regress141275.mm @@ -107,7 +107,7 @@ - (instancetype)init { self = [super init]; if (self) { - _jsSourcePerformQueue = dispatch_queue_create("JSTEval", DISPATCH_QUEUE_CONCURRENT); + _jsSourcePerformQueue = dispatch_queue_create("JSTEval", concurrentQueueWithAutoreleasePoolAttrSingleton()); _allScriptsDone = dispatch_semaphore_create(0); diff --git a/Source/JavaScriptCore/inspector/remote/cocoa/RemoteInspectorCocoa.mm b/Source/JavaScriptCore/inspector/remote/cocoa/RemoteInspectorCocoa.mm index 404bc4fad14d..469033e6b96b 100644 --- a/Source/JavaScriptCore/inspector/remote/cocoa/RemoteInspectorCocoa.mm +++ b/Source/JavaScriptCore/inspector/remote/cocoa/RemoteInspectorCocoa.mm @@ -127,7 +127,7 @@ static bool canAccessWebInspectorMachPort() } RemoteInspector::RemoteInspector() - : m_xpcQueue(adoptOSObject(dispatch_queue_create("com.apple.JavaScriptCore.remote-inspector-xpc", DISPATCH_QUEUE_SERIAL))) + : m_xpcQueue(adoptOSObject(dispatch_queue_create("com.apple.JavaScriptCore.remote-inspector-xpc", serialQueueWithAutoreleasePoolAttrSingleton()))) { } diff --git a/Source/JavaScriptCore/jsc.cpp b/Source/JavaScriptCore/jsc.cpp index cd4e4bd830ed..b18725343251 100644 --- a/Source/JavaScriptCore/jsc.cpp +++ b/Source/JavaScriptCore/jsc.cpp @@ -128,6 +128,7 @@ #include #include #include +#include #endif #if PLATFORM(GTK) @@ -4648,7 +4649,7 @@ int jscmain(int argc, char** argv) auto& memoryPressureHandler = MemoryPressureHandler::singleton(); { // FIXME: This is a false positive. rdar://160931336 - SUPPRESS_RETAINPTR_CTOR_ADOPT auto queue = adoptOSObject(dispatch_queue_create("jsc shell memory pressure handler", DISPATCH_QUEUE_SERIAL)); + SUPPRESS_RETAINPTR_CTOR_ADOPT OSObjectPtr queue = adoptOSObject(dispatch_queue_create("jsc shell memory pressure handler", serialQueueWithAutoreleasePoolAttrSingleton())); memoryPressureHandler.setDispatchQueue(WTF::move(queue)); } Box memoryPressureCriticalState = Box::create(Critical::No); diff --git a/Source/WTF/wtf/cocoa/WorkQueueCocoa.cpp b/Source/WTF/wtf/cocoa/WorkQueueCocoa.cpp index 2c08b5a44558..486e46022034 100644 --- a/Source/WTF/wtf/cocoa/WorkQueueCocoa.cpp +++ b/Source/WTF/wtf/cocoa/WorkQueueCocoa.cpp @@ -82,7 +82,7 @@ WorkQueueBase::WorkQueueBase(OSObjectPtr&& dispatchQueue) void WorkQueueBase::platformInitialize(ASCIILiteral name, Type type, QOS qos) { - dispatch_queue_attr_t attr = type == Type::Concurrent ? DISPATCH_QUEUE_CONCURRENT : DISPATCH_QUEUE_SERIAL; + dispatch_queue_attr_t attr = type == Type::Concurrent ? concurrentQueueWithAutoreleasePoolAttrSingleton() : serialQueueWithAutoreleasePoolAttrSingleton(); attr = dispatch_queue_attr_make_with_qos_class(attr, Thread::dispatchQOSClass(qos), 0); // FIXME: This is a false positive. rdar://160931336 SUPPRESS_RETAINPTR_CTOR_ADOPT lazyInitialize(m_dispatchQueue, adoptOSObject(dispatch_queue_create(name, attr))); diff --git a/Source/WTF/wtf/darwin/DispatchExtras.h b/Source/WTF/wtf/darwin/DispatchExtras.h index dfac7c6d6671..07f4e7e2aa29 100644 --- a/Source/WTF/wtf/darwin/DispatchExtras.h +++ b/Source/WTF/wtf/darwin/DispatchExtras.h @@ -39,7 +39,19 @@ inline dispatch_queue_main_t mainDispatchQueueSingleton() return dispatch_get_main_queue(); // NOLINT } +inline dispatch_queue_attr_t serialQueueWithAutoreleasePoolAttrSingleton() +{ + return DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL; // NOLINT +} + +inline dispatch_queue_attr_t concurrentQueueWithAutoreleasePoolAttrSingleton() +{ + return DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL; // NOLINT +} + } // namespace WTF +using WTF::concurrentQueueWithAutoreleasePoolAttrSingleton; using WTF::globalDispatchQueueSingleton; using WTF::mainDispatchQueueSingleton; +using WTF::serialQueueWithAutoreleasePoolAttrSingleton; diff --git a/Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.mm b/Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.mm index aa0f0d542919..9f9f515b93ed 100644 --- a/Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.mm +++ b/Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.mm @@ -44,6 +44,7 @@ #import #import #import +#import #import static inline NSData *replacementDataFromDecisionInfo(NSDictionary *decisionInfo) @@ -70,7 +71,7 @@ { ASSERT(!m_queue); ASSERT(!m_neFilterSource); - m_queue = adoptOSObject(dispatch_queue_create("WebKit NetworkExtension Filtering", DISPATCH_QUEUE_SERIAL)); + m_queue = adoptOSObject(dispatch_queue_create("WebKit NetworkExtension Filtering", serialQueueWithAutoreleasePoolAttrSingleton())); ASSERT_UNUSED(url, !url); m_neFilterSource = adoptNS([[NEFilterSource alloc] initWithDecisionQueue:m_queue.get()]); [m_neFilterSource setSourceAppIdentifier:applicationBundleIdentifier().createNSString().get()]; diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm index cac62c6b5823..c8d3921e0271 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm @@ -263,7 +263,7 @@ static String toString(const AVPlayerTimeControlStatus status) static dispatch_queue_t globalLoaderDelegateQueue() { - static NeverDestroyed> globalQueue = adoptOSObject(dispatch_queue_create("WebCoreAVFLoaderDelegate queue", DISPATCH_QUEUE_SERIAL)); + static NeverDestroyed> globalQueue = adoptOSObject(dispatch_queue_create("WebCoreAVFLoaderDelegate queue", serialQueueWithAutoreleasePoolAttrSingleton())); return globalQueue.get().get(); } diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm b/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm index 075df2bace22..f1f46577b8fe 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -120,7 +121,7 @@ - (void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary static dispatch_queue_t globalOutputDelegateQueue() { - static NeverDestroyed> globalQueue = adoptOSObject(dispatch_queue_create("WebQueuedVideoOutputDelegate queue", DISPATCH_QUEUE_SERIAL)); + static NeverDestroyed> globalQueue = adoptOSObject(dispatch_queue_create("WebQueuedVideoOutputDelegate queue", serialQueueWithAutoreleasePoolAttrSingleton())); return globalQueue.get().get(); } diff --git a/Source/WebCore/platform/graphics/cocoa/PeriodicSharedTimer.mm b/Source/WebCore/platform/graphics/cocoa/PeriodicSharedTimer.mm index 274ad9f4fdc3..4e8de0985d94 100644 --- a/Source/WebCore/platform/graphics/cocoa/PeriodicSharedTimer.mm +++ b/Source/WebCore/platform/graphics/cocoa/PeriodicSharedTimer.mm @@ -29,13 +29,14 @@ #import #import #import +#import namespace WebCore { WTF_MAKE_TZONE_ALLOCATED_IMPL(PeriodicSharedTimer); PeriodicSharedTimer::PeriodicSharedTimer(Seconds interval) - : m_queue(adoptOSObject(dispatch_queue_create("WebCore PeriodicSharedTimer", DISPATCH_QUEUE_SERIAL))) + : m_queue(adoptOSObject(dispatch_queue_create("WebCore PeriodicSharedTimer", serialQueueWithAutoreleasePoolAttrSingleton()))) , m_timer(adoptOSObject(dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, m_queue.get()))) { auto intervalNs = interval.nanosecondsAs(); diff --git a/Source/WebCore/platform/mac/PowerObserverMac.cpp b/Source/WebCore/platform/mac/PowerObserverMac.cpp index ba92b1158ecf..acc479d945f7 100644 --- a/Source/WebCore/platform/mac/PowerObserverMac.cpp +++ b/Source/WebCore/platform/mac/PowerObserverMac.cpp @@ -29,6 +29,7 @@ #import "PowerObserverMac.h" #import #import +#import namespace WebCore { @@ -41,7 +42,7 @@ PowerObserver::PowerObserver(Function&& powerOnHander) , m_notifierReference(0) { // FIXME: This is a false positive for dispatch_queue_create. rdar://160931336 - SUPPRESS_RETAINPTR_CTOR_ADOPT m_dispatchQueue = adoptOSObject(dispatch_queue_create("com.apple.WebKit.PowerObserver", 0)); + SUPPRESS_RETAINPTR_CTOR_ADOPT m_dispatchQueue = adoptOSObject(dispatch_queue_create("com.apple.WebKit.PowerObserver", serialQueueWithAutoreleasePoolAttrSingleton())); m_powerConnection = IORegisterForSystemPower(this, &m_notificationPort, [](void* context, io_service_t service, uint32_t messageType, void* messageArgument) { static_cast(context)->didReceiveSystemPowerNotification(service, messageType, messageArgument); }, &m_notifierReference); diff --git a/Source/WebCore/platform/mediastream/cocoa/AVVideoCaptureSource.mm b/Source/WebCore/platform/mediastream/cocoa/AVVideoCaptureSource.mm index 39b27d166578..99921b0bde64 100644 --- a/Source/WebCore/platform/mediastream/cocoa/AVVideoCaptureSource.mm +++ b/Source/WebCore/platform/mediastream/cocoa/AVVideoCaptureSource.mm @@ -126,7 +126,7 @@ static CMVideoDimensions NODELETE toCMVideoDimensions(const IntSize& size) static dispatch_queue_t globaVideoCaptureSerialQueue() { - static NeverDestroyed> globalQueue = adoptOSObject(dispatch_queue_create_with_target("WebCoreAVVideoCaptureSource video capture queue", DISPATCH_QUEUE_SERIAL, globalDispatchQueueSingleton(DISPATCH_QUEUE_PRIORITY_HIGH, 0))); + static NeverDestroyed> globalQueue = adoptOSObject(dispatch_queue_create_with_target("WebCoreAVVideoCaptureSource video capture queue", serialQueueWithAutoreleasePoolAttrSingleton(), globalDispatchQueueSingleton(DISPATCH_QUEUE_PRIORITY_HIGH, 0))); return globalQueue.get().get(); } diff --git a/Source/WebCore/platform/mediastream/cocoa/ScreenCaptureKitCaptureSource.mm b/Source/WebCore/platform/mediastream/cocoa/ScreenCaptureKitCaptureSource.mm index 99e3e5dbf9bc..123892abe49b 100644 --- a/Source/WebCore/platform/mediastream/cocoa/ScreenCaptureKitCaptureSource.mm +++ b/Source/WebCore/platform/mediastream/cocoa/ScreenCaptureKitCaptureSource.mm @@ -43,6 +43,7 @@ #import #import #import +#import #import #import @@ -639,7 +640,7 @@ - (void)outputVideoEffectDidStopForStream:(SCStream *)stream dispatch_queue_t ScreenCaptureKitCaptureSource::captureQueue() { if (!m_captureQueue) - m_captureQueue = adoptOSObject(dispatch_queue_create("CGDisplayStreamCaptureSource Capture Queue", DISPATCH_QUEUE_SERIAL)); + m_captureQueue = adoptOSObject(dispatch_queue_create("CGDisplayStreamCaptureSource Capture Queue", serialQueueWithAutoreleasePoolAttrSingleton())); return m_captureQueue.get(); } diff --git a/Source/WebKit/NetworkProcess/webrtc/NetworkRTCTCPSocketCocoa.mm b/Source/WebKit/NetworkProcess/webrtc/NetworkRTCTCPSocketCocoa.mm index 4d0d1ad1e1a5..e40222861276 100644 --- a/Source/WebKit/NetworkProcess/webrtc/NetworkRTCTCPSocketCocoa.mm +++ b/Source/WebKit/NetworkProcess/webrtc/NetworkRTCTCPSocketCocoa.mm @@ -39,6 +39,7 @@ #include #include #include +#include #include WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN @@ -52,7 +53,7 @@ static dispatch_queue_t tcpSocketQueueSingleton() { - static NeverDestroyed> queue = adoptOSObject(dispatch_queue_create("WebRTC TCP socket queue", DISPATCH_QUEUE_SERIAL)); + static NeverDestroyed> queue = adoptOSObject(dispatch_queue_create("WebRTC TCP socket queue", serialQueueWithAutoreleasePoolAttrSingleton())); return queue.get().get(); } diff --git a/Source/WebKit/NetworkProcess/webrtc/NetworkRTCUDPSocketCocoa.mm b/Source/WebKit/NetworkProcess/webrtc/NetworkRTCUDPSocketCocoa.mm index cfeab5d05cfb..e13b8142d2c3 100644 --- a/Source/WebKit/NetworkProcess/webrtc/NetworkRTCUDPSocketCocoa.mm +++ b/Source/WebKit/NetworkProcess/webrtc/NetworkRTCUDPSocketCocoa.mm @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -118,7 +119,7 @@ void NODELETE decrementPendingSendCount() static dispatch_queue_t udpSocketQueueSingleton() { - static NeverDestroyed> queue = adoptOSObject(dispatch_queue_create("WebRTC UDP socket queue", OSObjectPtr { DISPATCH_QUEUE_CONCURRENT }.get())); + static NeverDestroyed> queue = adoptOSObject(dispatch_queue_create("WebRTC UDP socket queue", concurrentQueueWithAutoreleasePoolAttrSingleton())); return queue.get().get(); } diff --git a/Source/WebKit/UIProcess/Cocoa/WKScreenTimeConfigurationObserver.mm b/Source/WebKit/UIProcess/Cocoa/WKScreenTimeConfigurationObserver.mm index c61d2e27897c..20d7b095ec78 100644 --- a/Source/WebKit/UIProcess/Cocoa/WKScreenTimeConfigurationObserver.mm +++ b/Source/WebKit/UIProcess/Cocoa/WKScreenTimeConfigurationObserver.mm @@ -41,7 +41,7 @@ static dispatch_queue_t screenTimeUpdateQueueSingleton() { - static NeverDestroyed> queue = adoptOSObject(dispatch_queue_create("com.apple.WebKit.ScreenTimeUpdateQueue", DISPATCH_QUEUE_SERIAL)); + static NeverDestroyed> queue = adoptOSObject(dispatch_queue_create("com.apple.WebKit.ScreenTimeUpdateQueue", serialQueueWithAutoreleasePoolAttrSingleton())); return queue.get().get(); } diff --git a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm index b0c42f071a45..075ac7291d49 100644 --- a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm +++ b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm @@ -290,7 +290,7 @@ static AccessibilityPreferences accessibilityPreferences() #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK) void WebProcessPool::setMediaAccessibilityPreferences(WebProcessProxy& process) { - static NeverDestroyed> mediaAccessibilityQueue = adoptOSObject(dispatch_queue_create("MediaAccessibility queue", DISPATCH_QUEUE_SERIAL)); + static NeverDestroyed> mediaAccessibilityQueue = adoptOSObject(dispatch_queue_create("MediaAccessibility queue", serialQueueWithAutoreleasePoolAttrSingleton())); dispatch_async(mediaAccessibilityQueue.get().get(), [weakThis = WeakPtr { *this }, weakProcess = WeakPtr { process }] mutable { auto captionDisplayMode = WebCore::CaptionUserPreferencesMediaAF::platformCaptionDisplayMode(); diff --git a/Source/WebKit/UIProcess/XR/ios/WKARPresentationSession.mm b/Source/WebKit/UIProcess/XR/ios/WKARPresentationSession.mm index 253cf4900c91..887607d4d3fa 100644 --- a/Source/WebKit/UIProcess/XR/ios/WKARPresentationSession.mm +++ b/Source/WebKit/UIProcess/XR/ios/WKARPresentationSession.mm @@ -36,6 +36,7 @@ #import #import #import +#import #import #import @@ -396,7 +397,7 @@ - (nullable instancetype)initWithSession:(_WKARPresentationSession *)session { self = [super init]; if (self) { - _accessQueue = adoptOSObject(dispatch_queue_create("com.apple.WebContent._WKTransientGestureRecognizer.AccessQueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL)); + _accessQueue = adoptOSObject(dispatch_queue_create("com.apple.WebContent._WKTransientGestureRecognizer.AccessQueue", serialQueueWithAutoreleasePoolAttrSingleton())); _session = session; _transientActions = adoptNS([NSMutableDictionary new]); } diff --git a/Source/WebKit/UIProcess/XR/xros/WKXRTrackingManager.mm b/Source/WebKit/UIProcess/XR/xros/WKXRTrackingManager.mm index e8b2065859bf..062d74f32c71 100644 --- a/Source/WebKit/UIProcess/XR/xros/WKXRTrackingManager.mm +++ b/Source/WebKit/UIProcess/XR/xros/WKXRTrackingManager.mm @@ -31,6 +31,7 @@ #import "Logging.h" #import #import +#import #import @@ -101,7 +102,7 @@ - (instancetype)initWithHandTrackingEnabled:(BOOL)handTrackingEnabled layerRende if (!(self = [super init])) return nil; - _accessQueue = dispatch_queue_create("com.apple.WebContent.WKXRTrackingManager.AccessQueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + _accessQueue = dispatch_queue_create("com.apple.WebContent.WKXRTrackingManager.AccessQueue", serialQueueWithAutoreleasePoolAttrSingleton()); #if ENABLE(WEBXR_HANDS) && !PLATFORM(IOS_FAMILY_SIMULATOR) _handTrackingEnabled = handTrackingEnabled; #else diff --git a/Source/WebKit/UIProcess/ios/WKWebGeolocationPolicyDeciderIOS.mm b/Source/WebKit/UIProcess/ios/WKWebGeolocationPolicyDeciderIOS.mm index 3255de3e7921..5f3396120a8e 100644 --- a/Source/WebKit/UIProcess/ios/WKWebGeolocationPolicyDeciderIOS.mm +++ b/Source/WebKit/UIProcess/ios/WKWebGeolocationPolicyDeciderIOS.mm @@ -132,7 +132,7 @@ - (id)init if (!self) return nil; - _diskDispatchQueue = adoptOSObject(dispatch_queue_create("com.apple.WebKit.WKWebGeolocationPolicyDecider", DISPATCH_QUEUE_SERIAL)); + _diskDispatchQueue = adoptOSObject(dispatch_queue_create("com.apple.WebKit.WKWebGeolocationPolicyDecider", serialQueueWithAutoreleasePoolAttrSingleton())); CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenterSingleton(), self, clearGeolocationCache, protect(CLAppResetChangedNotification), NULL, CFNotificationSuspensionBehaviorCoalesce); diff --git a/Source/WebKit/UIProcess/mac/ServicesController.mm b/Source/WebKit/UIProcess/mac/ServicesController.mm index e3af14515ee7..5972b569a26c 100644 --- a/Source/WebKit/UIProcess/mac/ServicesController.mm +++ b/Source/WebKit/UIProcess/mac/ServicesController.mm @@ -46,7 +46,7 @@ } ServicesController::ServicesController() - : m_refreshQueue(adoptOSObject(dispatch_queue_create("com.apple.WebKit.ServicesController", DISPATCH_QUEUE_SERIAL))) + : m_refreshQueue(adoptOSObject(dispatch_queue_create("com.apple.WebKit.ServicesController", serialQueueWithAutoreleasePoolAttrSingleton()))) , m_hasPendingRefresh(false) , m_hasImageServices(false) , m_hasSelectionServices(false) diff --git a/Source/WebKit/webpushd/_WKMockUserNotificationCenter.mm b/Source/WebKit/webpushd/_WKMockUserNotificationCenter.mm index 9fa93b886f2c..e6972095acf3 100644 --- a/Source/WebKit/webpushd/_WKMockUserNotificationCenter.mm +++ b/Source/WebKit/webpushd/_WKMockUserNotificationCenter.mm @@ -28,6 +28,7 @@ #import #import +#import #import #if HAVE(FULL_FEATURED_USER_NOTIFICATIONS) @@ -60,7 +61,7 @@ - (instancetype)initWithBundleIdentifierInternal:(NSString *)bundleIdentifier if (!self) return nil; - m_queue = adoptOSObject(dispatch_queue_create(nullptr, OSObjectPtr { DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL }.get())); + m_queue = adoptOSObject(dispatch_queue_create(nullptr, serialQueueWithAutoreleasePoolAttrSingleton())); m_bundleIdentifier = bundleIdentifier; m_notifications = adoptNS([[NSMutableArray alloc] init]); diff --git a/Source/WebKitLegacy/mac/WebView/WebPreferences.mm b/Source/WebKitLegacy/mac/WebView/WebPreferences.mm index 99237c363f64..518c435323d4 100644 --- a/Source/WebKitLegacy/mac/WebView/WebPreferences.mm +++ b/Source/WebKitLegacy/mac/WebView/WebPreferences.mm @@ -56,6 +56,7 @@ #import #import #import +#import #import @@ -119,7 +120,7 @@ + (NSString *)_IBCreatorID; public: WebPreferencesPrivate() #if PLATFORM(IOS_FAMILY) - : readWriteQueue { adoptOSObject(dispatch_queue_create("com.apple.WebPreferences.ReadWriteQueue", DISPATCH_QUEUE_CONCURRENT)) } + : readWriteQueue { adoptOSObject(dispatch_queue_create("com.apple.WebPreferences.ReadWriteQueue", concurrentQueueWithAutoreleasePoolAttrSingleton())) } #endif { } diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp.py b/Tools/Scripts/webkitpy/style/checkers/cpp.py index 1898f39429ea..4f1145062df6 100644 --- a/Tools/Scripts/webkitpy/style/checkers/cpp.py +++ b/Tools/Scripts/webkitpy/style/checkers/cpp.py @@ -4502,6 +4502,18 @@ def check_language(filename, clean_lines, line_number, file_extension, include_s error(line_number, 'runtime/dispatch_set_target_queue', 5, 'Never use dispatch_set_target_queue. Use dispatch_queue_create_with_target instead.') + matched = search(r'\bDISPATCH_QUEUE_(SERIAL|CONCURRENT)\b', line) + if matched: + error(line_number, 'runtime/dispatch_queue_autorelease_pool', 5, + 'Use %sQueueWithAutoreleasePoolAttrSingleton() instead of %s so that each work item runs with its ' + 'own autorelease pool.' % (matched.group(1).lower(), matched.group(0))) + + matched = search(r'\bDISPATCH_QUEUE_(SERIAL|CONCURRENT)_WITH_AUTORELEASE_POOL\b', line) + if matched: + error(line_number, 'runtime/dispatch_queue_autorelease_pool', 5, + 'Use %sQueueWithAutoreleasePoolAttrSingleton() instead of %s so that static analysis knows the ' + 'attribute does not need to be retained.' % (matched.group(1).lower(), matched.group(0))) + matched = search(r'\b(RetainPtr<.*)', line) if matched: match_line = matched.group(1) @@ -5345,6 +5357,7 @@ class CppChecker(object): 'runtime/callonmainthread', 'runtime/casting', 'runtime/ctype_function', + 'runtime/dispatch_queue_autorelease_pool', 'runtime/dispatch_set_target_queue', 'runtime/enum_bitfields', 'runtime/explicit', diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py index ff07d83aeaec..88c3aba72aeb 100644 --- a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py +++ b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py @@ -1868,11 +1868,36 @@ def test_insecure_temp_file(self): def test_dispatch_set_target_queue(self): self.assert_lint( '''\ - globalQueue = dispatch_queue_create("My Serial Queue", DISPATCH_QUEUE_SERIAL); + globalQueue = dispatch_queue_create("My Serial Queue", serialQueueWithAutoreleasePoolAttrSingleton()); dispatch_set_target_queue(globalQueue, globalDispatchQueueSingleton(DISPATCH_QUEUE_PRIORITY_HIGH, 0));''', 'Never use dispatch_set_target_queue. Use dispatch_queue_create_with_target instead.' ' [runtime/dispatch_set_target_queue] [5]') - self.assert_lint('globalQueue = dispatch_queue_create_with_target("My Serial Queue", DISPATCH_QUEUE_SERIAL, globalDispatchQueueSingleton(DISPATCH_QUEUE_PRIORITY_HIGH, 0));', '') + self.assert_lint('globalQueue = dispatch_queue_create_with_target("My Serial Queue", serialQueueWithAutoreleasePoolAttrSingleton(), globalDispatchQueueSingleton(DISPATCH_QUEUE_PRIORITY_HIGH, 0));', '') + + def test_dispatch_queue_autorelease_pool(self): + self.assert_lint( + 'globalQueue = dispatch_queue_create("My Serial Queue", DISPATCH_QUEUE_SERIAL);', + 'Use serialQueueWithAutoreleasePoolAttrSingleton() instead of DISPATCH_QUEUE_SERIAL so that each work ' + 'item runs with its own autorelease pool.' + ' [runtime/dispatch_queue_autorelease_pool] [5]') + self.assert_lint( + 'globalQueue = dispatch_queue_create("My Concurrent Queue", DISPATCH_QUEUE_CONCURRENT);', + 'Use concurrentQueueWithAutoreleasePoolAttrSingleton() instead of DISPATCH_QUEUE_CONCURRENT so that ' + 'each work item runs with its own autorelease pool.' + ' [runtime/dispatch_queue_autorelease_pool] [5]') + self.assert_lint( + 'globalQueue = dispatch_queue_create("My Serial Queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);', + 'Use serialQueueWithAutoreleasePoolAttrSingleton() instead of DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL ' + 'so that static analysis knows the attribute does not need to be retained.' + ' [runtime/dispatch_queue_autorelease_pool] [5]') + self.assert_lint( + 'globalQueue = dispatch_queue_create("My Concurrent Queue", DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL);', + 'Use concurrentQueueWithAutoreleasePoolAttrSingleton() instead of ' + 'DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL so that static analysis knows the attribute does not ' + 'need to be retained.' + ' [runtime/dispatch_queue_autorelease_pool] [5]') + self.assert_lint('globalQueue = dispatch_queue_create("My Serial Queue", serialQueueWithAutoreleasePoolAttrSingleton());', '') + self.assert_lint('globalQueue = dispatch_queue_create("My Concurrent Queue", concurrentQueueWithAutoreleasePoolAttrSingleton());', '') def test_retainptr_pointer(self): self.assert_lint( @@ -6213,7 +6238,7 @@ def test_auto_with_adopt(self): " [runtime/auto_with_adopt] [4]", 'foo.cpp') self.assert_lint( - 'auto queue = adoptOSObject(dispatch_queue_create("foo", DISPATCH_QUEUE_SERIAL));', + 'auto queue = adoptOSObject(dispatch_queue_create("foo", serialQueueWithAutoreleasePoolAttrSingleton()));', "Use 'OSObjectPtr' instead of 'auto' with 'adoptOSObject()'." " [runtime/auto_with_adopt] [4]", 'foo.cpp') @@ -6526,16 +6551,16 @@ def test_wtf_never_destroyed(self): def test_wtf_os_object_ptr(self): self.assert_lint( - 'auto queue = adoptOSObject(dispatch_queue_create("foo", DISPATCH_QUEUE_SERIAL));', + 'auto queue = adoptOSObject(dispatch_queue_create("foo", serialQueueWithAutoreleasePoolAttrSingleton()));', "Use 'OSObjectPtr' instead of 'auto' with 'adoptOSObject()'." " [runtime/auto_with_adopt] [4]", 'foo.cpp') self.assert_lint( - 'OSObjectPtr queue = adoptOSObject(dispatch_queue_create("foo", DISPATCH_QUEUE_SERIAL));', + 'OSObjectPtr queue = adoptOSObject(dispatch_queue_create("foo", serialQueueWithAutoreleasePoolAttrSingleton()));', '', 'foo.cpp') self.assert_lint( - 'OSObjectPtr queue = adoptOSObject(dispatch_queue_create("foo", DISPATCH_QUEUE_SERIAL));', + 'OSObjectPtr queue = adoptOSObject(dispatch_queue_create("foo", serialQueueWithAutoreleasePoolAttrSingleton()));', '', 'foo.cpp') self.assert_lint( @@ -6564,7 +6589,7 @@ def test_wtf_os_object_ptr(self): " [runtime/wtf_os_object_ptr] [4]", 'foo.mm') self.assert_lint_one_of_many_errors_re( - 'auto queue = adoptNS(dispatch_queue_create("foo", DISPATCH_QUEUE_SERIAL));', + 'auto queue = adoptNS(dispatch_queue_create("foo", serialQueueWithAutoreleasePoolAttrSingleton()));', r"Use 'adoptOSObject\(\)' instead of 'adoptNS\(\)' for dispatch objects.", 'foo.mm') self.assert_lint_one_of_many_errors_re( @@ -6572,19 +6597,19 @@ def test_wtf_os_object_ptr(self): r"Use 'adoptOSObject\(\)' instead of 'adoptNS\(\)' for dispatch objects.", 'foo.mm') self.assert_lint_one_of_many_errors_re( - 'auto queue = adoptOSObject(dispatch_queue_create("foo", RetainPtr { DISPATCH_QUEUE_CONCURRENT }.get()));', + 'auto queue = adoptOSObject(dispatch_queue_create("foo", RetainPtr { DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL }.get()));', r"Use 'OSObjectPtr' instead of 'RetainPtr' for dispatch objects.", 'foo.mm') self.assert_lint_one_of_many_errors_re( - 'auto queue = adoptOSObject(dispatch_queue_create("foo", RetainPtr { DISPATCH_QUEUE_SERIAL }.get()));', + 'auto queue = adoptOSObject(dispatch_queue_create("foo", RetainPtr { DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL }.get()));', r"Use 'OSObjectPtr' instead of 'RetainPtr' for dispatch objects.", 'foo.mm') self.assert_lint_one_of_many_errors_re( - 'auto queue = adoptOSObject(dispatch_queue_create("foo", retainPtr(DISPATCH_QUEUE_CONCURRENT).get()));', + 'auto queue = adoptOSObject(dispatch_queue_create("foo", retainPtr(DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL).get()));', r"Use 'OSObjectPtr' instead of 'RetainPtr' for dispatch objects.", 'foo.mm') self.assert_lint_one_of_many_errors_re( - 'auto queue = adoptOSObject(dispatch_queue_create("foo", retainPtr(DISPATCH_QUEUE_SERIAL).get()));', + 'auto queue = adoptOSObject(dispatch_queue_create("foo", retainPtr(DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL).get()));', r"Use 'OSObjectPtr' instead of 'RetainPtr' for dispatch objects.", 'foo.mm') diff --git a/Tools/TestWebKitAPI/Helpers/mac/VirtualGamepad.mm b/Tools/TestWebKitAPI/Helpers/mac/VirtualGamepad.mm index 61c795ebccaf..b526d4677cbf 100644 --- a/Tools/TestWebKitAPI/Helpers/mac/VirtualGamepad.mm +++ b/Tools/TestWebKitAPI/Helpers/mac/VirtualGamepad.mm @@ -43,7 +43,7 @@ VirtualGamepad::VirtualGamepad(const GamepadMapping& gamepadMapping) : m_gamepadMapping(gamepadMapping) { - m_dispatchQueue = adoptOSObject(dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL)); + m_dispatchQueue = adoptOSObject(dispatch_queue_create(0, serialQueueWithAutoreleasePoolAttrSingleton())); m_uniqueID = NSUUID.UUID.UUIDString; m_buttonValues = Vector(FillWith { }, m_gamepadMapping.buttonCount, 0.0); diff --git a/Tools/TestWebKitAPI/Tests/WTF/darwin/OSObjectPtr.cpp b/Tools/TestWebKitAPI/Tests/WTF/darwin/OSObjectPtr.cpp index 31da0a68a64c..38978a727b6b 100644 --- a/Tools/TestWebKitAPI/Tests/WTF/darwin/OSObjectPtr.cpp +++ b/Tools/TestWebKitAPI/Tests/WTF/darwin/OSObjectPtr.cpp @@ -26,6 +26,7 @@ #include "config.h" #include +#include #include #if __has_feature(objc_arc) && !defined(NDEBUG) @@ -45,7 +46,7 @@ namespace TestWebKitAPI { TEST(OS_OBJECT_PTR_TEST_NAME, AdoptOSObject) { - OSObjectPtr foo = adoptOSObject(dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL)); + OSObjectPtr foo = adoptOSObject(dispatch_queue_create(0, serialQueueWithAutoreleasePoolAttrSingleton())); uintptr_t fooPtr; AUTORELEASEPOOL_FOR_ARC_DEBUG { fooPtr = reinterpret_cast(foo.get()); @@ -55,7 +56,7 @@ TEST(OS_OBJECT_PTR_TEST_NAME, AdoptOSObject) TEST(OS_OBJECT_PTR_TEST_NAME, RetainRelease) { - dispatch_queue_t foo = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL); + dispatch_queue_t foo = dispatch_queue_create(0, serialQueueWithAutoreleasePoolAttrSingleton()); auto fooPtr = reinterpret_cast(foo); EXPECT_EQ(1, CFGetRetainCount((CFTypeRef)fooPtr)); @@ -74,7 +75,7 @@ TEST(OS_OBJECT_PTR_TEST_NAME, RetainRelease) TEST(OS_OBJECT_PTR_TEST_NAME, LeakRef) { - OSObjectPtr foo = adoptOSObject(dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL)); + OSObjectPtr foo = adoptOSObject(dispatch_queue_create(0, serialQueueWithAutoreleasePoolAttrSingleton())); uintptr_t fooPtr; AUTORELEASEPOOL_FOR_ARC_DEBUG { fooPtr = reinterpret_cast(foo.get()); From 119fd8268d356250ef15a870e5dd136fee765972 Mon Sep 17 00:00:00 2001 From: Ben Nham Date: Fri, 28 Aug 2026 08:13:48 -0700 Subject: [PATCH 035/103] [Site Isolation] Coalesce per-rendering-update frame geometry IPCs https://bugs.webkit.org/show_bug.cgi?id=322691 rdar://problem/185961490 Reviewed by Alex Christensen and Kiet Ho. When Site Isolation is enabled, we currently send three separate frame geometry related IPCs in Page::syncLocalFrameInfoToRemote(). Consolidate these in to a single IPC instead. * Source/WebCore/Headers.cmake: * Source/WebCore/Sources.txt: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/page/Frame.cpp: (WebCore::Frame::updateFrameTreeSyncData): * Source/WebCore/page/FrameGeometrySyncData.cpp: Added. (WebCore::operator<<): * Source/WebCore/page/FrameGeometrySyncData.h: Added. * Source/WebCore/page/FrameTreeSyncData.in: * Source/WebCore/page/LocalFrameView.cpp: (WebCore::LocalFrameView::updateLayoutViewportRect): Deleted. (WebCore::LocalFrameView::updateContentsSizeForRemoteFrames): Deleted. * Source/WebCore/page/LocalFrameView.h: * Source/WebCore/page/Page.cpp: (WebCore::Page::syncLocalFrameInfoToRemote): * Source/WebCore/page/RemoteFrame.cpp: (WebCore::RemoteFrame::usedZoomForChild const): * Source/WebCore/page/RemoteFrameLayoutInfo.cpp: (WebCore::operator<<): * Source/WebCore/page/RemoteFrameLayoutInfo.h: * Source/WebCore/page/RemoteFrameView.cpp: (WebCore::RemoteFrameView::layoutViewportRect const): (WebCore::RemoteFrameView::contentsSize const): (WebCore::RemoteFrameView::visibleRectOfChild const): (WebCore::RemoteFrameView::appearanceOfOwnerElementOfChildFrame const): (WebCore::RemoteFrameView::childFrameOwnerContentBoxLocation const): (WebCore::RemoteFrameView::childFrameOwnerToRootContentTransform const): (WebCore::RemoteFrameView::absoluteToChildFrameOwnerLocalTransform const): * Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in: * Source/WebKit/UIProcess/WebFrameProxy.cpp: (WebKit::WebFrameProxy::calculateFrameTreeSyncData const): * Source/WebKit/WebProcess/WebPage/WebPage.cpp: (WebKit::WebPage::frameTreeSyncDataChangedInAnotherProcess): (WebKit::WebPage::updateChildFrameVisibleRectsFromParent): Canonical link: https://commits.webkit.org/320043@main --- Source/WebCore/Headers.cmake | 1 + Source/WebCore/Sources.txt | 1 + .../WebCore/WebCore.xcodeproj/project.pbxproj | 6 +++ Source/WebCore/page/Frame.cpp | 11 +++- Source/WebCore/page/FrameGeometrySyncData.cpp | 46 +++++++++++++++++ Source/WebCore/page/FrameGeometrySyncData.h | 51 +++++++++++++++++++ Source/WebCore/page/FrameTreeSyncData.in | 7 +-- Source/WebCore/page/LocalFrameView.cpp | 10 ---- Source/WebCore/page/LocalFrameView.h | 2 - Source/WebCore/page/Page.cpp | 9 ++-- Source/WebCore/page/RemoteFrame.cpp | 2 +- Source/WebCore/page/RemoteFrameLayoutInfo.cpp | 32 ++++++++++++ Source/WebCore/page/RemoteFrameLayoutInfo.h | 3 ++ Source/WebCore/page/RemoteFrameView.cpp | 14 ++--- .../WebCoreArgumentCoders.serialization.in | 7 +++ Source/WebKit/UIProcess/WebFrameProxy.cpp | 2 +- Source/WebKit/WebProcess/WebPage/WebPage.cpp | 7 ++- 17 files changed, 174 insertions(+), 37 deletions(-) create mode 100644 Source/WebCore/page/FrameGeometrySyncData.cpp create mode 100644 Source/WebCore/page/FrameGeometrySyncData.h diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake index d094dd5a5aac..0682097ef19d 100644 --- a/Source/WebCore/Headers.cmake +++ b/Source/WebCore/Headers.cmake @@ -2026,6 +2026,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS page/FrameConsoleClient.h page/FrameDestructionObserver.h page/FrameDestructionObserverInlines.h + page/FrameGeometrySyncData.h page/FrameIdentifier.h page/FrameInlines.h page/FrameSnapshotting.h diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt index c442b83be6f3..f67d63443210 100644 --- a/Source/WebCore/Sources.txt +++ b/Source/WebCore/Sources.txt @@ -2290,6 +2290,7 @@ page/FocusController.cpp page/Frame.cpp page/FrameConsoleClient.cpp page/FrameDestructionObserver.cpp +page/FrameGeometrySyncData.cpp page/FrameIdentifier.cpp page/FrameSnapshotting.cpp @header:RenderStyleGetters @cost:6 page/FrameTree.cpp diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj index 8686501f17ae..0e55b4666785 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj @@ -411,6 +411,7 @@ 0F099D0917B968A100FF84B9 /* WebCoreTypedArrayController.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F099D0717B968A100FF84B9 /* WebCoreTypedArrayController.h */; settings = {ATTRIBUTES = (Private, ); }; }; 0F0E009C26F92D8400ACE9C6 /* ScrollAnimationMomentum.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F0E009A26F92D7B00ACE9C6 /* ScrollAnimationMomentum.h */; settings = {ATTRIBUTES = (Private, ); }; }; 0F11781822E3C47F008BD570 /* FrameIdentifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F11781622E3C47E008BD570 /* FrameIdentifier.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 81ED7B1F003A5A7768139C21 /* FrameGeometrySyncData.h in Headers */ = {isa = PBXBuildFile; fileRef = 26B3A704C67CF84C8688D269 /* FrameGeometrySyncData.h */; settings = {ATTRIBUTES = (Private, ); }; }; 0F11A54F0F39233100C37884 /* RenderSelectionGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F11A54E0F39233100C37884 /* RenderSelectionGeometry.h */; settings = {ATTRIBUTES = (Private, ); }; }; 0F13163E16ED0CC80035CC04 /* PlatformCAFilters.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F13163D16ED0CC80035CC04 /* PlatformCAFilters.h */; settings = {ATTRIBUTES = (Private, ); }; }; 0F1774801378B772009DA76A /* ScrollAnimatorIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F17747E1378B771009DA76A /* ScrollAnimatorIOS.h */; }; @@ -8385,6 +8386,8 @@ 0F0E009A26F92D7B00ACE9C6 /* ScrollAnimationMomentum.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScrollAnimationMomentum.h; sourceTree = ""; }; 0F0E009B26F92D7B00ACE9C6 /* ScrollAnimationMomentum.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScrollAnimationMomentum.cpp; sourceTree = ""; }; 0F11781622E3C47E008BD570 /* FrameIdentifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FrameIdentifier.h; sourceTree = ""; }; + 26B3A704C67CF84C8688D269 /* FrameGeometrySyncData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameGeometrySyncData.h; sourceTree = ""; }; + 3D9194F39EFB04C0917E9354 /* FrameGeometrySyncData.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FrameGeometrySyncData.cpp; sourceTree = ""; }; 0F11A54E0F39233100C37884 /* RenderSelectionGeometry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderSelectionGeometry.h; sourceTree = ""; }; 0F13163D16ED0CC80035CC04 /* PlatformCAFilters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlatformCAFilters.h; sourceTree = ""; }; 0F13163F16ED0CDE0035CC04 /* PlatformCAFiltersCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformCAFiltersCocoa.mm; sourceTree = ""; }; @@ -31299,6 +31302,8 @@ 974A862014B7ADBB003FDC76 /* FrameDestructionObserver.cpp */, 974A862114B7ADBB003FDC76 /* FrameDestructionObserver.h */, 46014ACB28333F52004C0B84 /* FrameDestructionObserverInlines.h */, + 3D9194F39EFB04C0917E9354 /* FrameGeometrySyncData.cpp */, + 26B3A704C67CF84C8688D269 /* FrameGeometrySyncData.h */, FA63420E2D9CB95B00A6BECE /* FrameIdentifier.cpp */, 0F11781622E3C47E008BD570 /* FrameIdentifier.h */, 9B46C04A2DC4A867002E22C4 /* FrameInlines.h */, @@ -45843,6 +45848,7 @@ 46014ACC28333F65004C0B84 /* FrameDestructionObserverInlines.h in Headers */, 5E3DD8092F903C9A00250DF5 /* FrameDOMAgent.h in Headers */, 5E3DD80C2F903C9A00250DF5 /* FrameDOMStorageAgent.h in Headers */, + 81ED7B1F003A5A7768139C21 /* FrameGeometrySyncData.h in Headers */, 0F11781822E3C47F008BD570 /* FrameIdentifier.h in Headers */, 9B46C04B2DC4A867002E22C4 /* FrameInlines.h in Headers */, F30EE46B2E721BA800935B60 /* FrameInspectorController.h in Headers */, diff --git a/Source/WebCore/page/Frame.cpp b/Source/WebCore/page/Frame.cpp index cdf8f3bf742a..45c25db33654 100644 --- a/Source/WebCore/page/Frame.cpp +++ b/Source/WebCore/page/Frame.cpp @@ -358,6 +358,11 @@ void Frame::updateFrameTreeSyncData(Ref&& data) void Frame::updateFrameTreeSyncData(const FrameTreeSyncSerializationData& data) { + if (static_cast(data.value.index()) != FrameTreeSyncDataType::FrameGeometry) { + protect(frameTreeSyncData())->update(data); + return; + } + auto invalidateChildFrameForDarkAppearanceChange = [&](const auto& oldMap, const auto& newMap) { for (RefPtr child = tree().firstChild(); child; child = child->tree().nextSibling()) { RefPtr localChild = dynamicDowncast(child); @@ -369,6 +374,8 @@ void Frame::updateFrameTreeSyncData(const FrameTreeSyncSerializationData& data) if (!oldFrameInfo || !newFrameInfo || oldFrameInfo->ownerElementAppearance().contains(FrameOwnerElementAppearance::IsDark) != newFrameInfo->ownerElementAppearance().contains(FrameOwnerElementAppearance::IsDark)) { RefPtr localChildView = localChild->view(); + if (!localChildView) + continue; localChildView->invalidateForFrameOwnerColorSchemeChange(); protect(localChildView->layoutContext())->scheduleLayout(); @@ -376,11 +383,11 @@ void Frame::updateFrameTreeSyncData(const FrameTreeSyncSerializationData& data) } }; - auto oldChildrenFrameLayoutMap = m_frameTreeSyncData->childrenFrameLayoutInfo; + auto oldChildrenFrameLayoutMap = m_frameTreeSyncData->frameGeometry.childrenFrameLayoutInfo; protect(frameTreeSyncData())->update(data); - invalidateChildFrameForDarkAppearanceChange(oldChildrenFrameLayoutMap, m_frameTreeSyncData->childrenFrameLayoutInfo); + invalidateChildFrameForDarkAppearanceChange(oldChildrenFrameLayoutMap, m_frameTreeSyncData->frameGeometry.childrenFrameLayoutInfo); } bool Frame::frameCanCreatePaymentSession() const diff --git a/Source/WebCore/page/FrameGeometrySyncData.cpp b/Source/WebCore/page/FrameGeometrySyncData.cpp new file mode 100644 index 000000000000..037019559188 --- /dev/null +++ b/Source/WebCore/page/FrameGeometrySyncData.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2026 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 "FrameGeometrySyncData.h" + +#include +#include + +namespace WebCore { + +WTF_MAKE_STRUCT_TZONE_ALLOCATED_IMPL(FrameGeometrySyncData); + +WTF::TextStream& operator<<(WTF::TextStream& ts, const FrameGeometrySyncData& data) +{ + WTF::TextStream::GroupScope scope(ts); + ts << "FrameGeometrySyncData"_s; + ts.dumpProperty("layoutViewportRect"_s, data.layoutViewportRect); + ts.dumpProperty("contentsSize"_s, data.contentsSize); + ts.dumpProperty("childrenFrameLayoutInfo"_s, data.childrenFrameLayoutInfo); + return ts; +} + +} // namespace WebCore diff --git a/Source/WebCore/page/FrameGeometrySyncData.h b/Source/WebCore/page/FrameGeometrySyncData.h new file mode 100644 index 000000000000..42f26b1ad48b --- /dev/null +++ b/Source/WebCore/page/FrameGeometrySyncData.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 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 +#include +#include +#include +#include +#include + +namespace WTF { +class TextStream; +} + +namespace WebCore { + +struct FrameGeometrySyncData { + WTF_MAKE_STRUCT_TZONE_ALLOCATED_EXPORT(FrameGeometrySyncData, WEBCORE_EXPORT); + + LayoutRect layoutViewportRect; + IntSize contentsSize; + HashMap> childrenFrameLayoutInfo; +}; + +WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, const FrameGeometrySyncData&); + +} // namespace WebCore diff --git a/Source/WebCore/page/FrameTreeSyncData.in b/Source/WebCore/page/FrameTreeSyncData.in index 3bfcaa2ecc29..e6b6ca7ebbd6 100644 --- a/Source/WebCore/page/FrameTreeSyncData.in +++ b/Source/WebCore/page/FrameTreeSyncData.in @@ -36,9 +36,4 @@ FrameScrollPosition : WebCore::ScrollPosition [Headers=] ## Layout information for Intersection Observer -FrameLayoutViewportRect : WebCore::LayoutRect [Headers=] - -FrameContentsSize : WebCore::IntSize [Headers=] - -# Collection of layout info regarding children frames of this frame. -ChildrenFrameLayoutInfo : HashMap> [Headers=,] +FrameGeometry : WebCore::FrameGeometrySyncData [Headers=] diff --git a/Source/WebCore/page/LocalFrameView.cpp b/Source/WebCore/page/LocalFrameView.cpp index ebdc74076023..2f2b2e242948 100644 --- a/Source/WebCore/page/LocalFrameView.cpp +++ b/Source/WebCore/page/LocalFrameView.cpp @@ -2050,16 +2050,6 @@ LayoutRect LocalFrameView::layoutViewportRect() const return LayoutRect(m_layoutViewportOrigin, baseLayoutViewportSize()); } -void LocalFrameView::updateLayoutViewportRect() -{ - m_frame->loader().client().broadcastFrameLayoutViewportRectToOtherProcesses(layoutViewportRect()); -} - -void LocalFrameView::updateContentsSizeForRemoteFrames() -{ - m_frame->loader().client().broadcastFrameContentsSizeToOtherProcesses(contentsSize()); -} - // visibleContentRect is in the bounds of the scroll view content. That consists of an // optional header, the document, and an optional footer. Only the document is scaled, // so we have to compute the visible part of the document in unscaled document coordinates. diff --git a/Source/WebCore/page/LocalFrameView.h b/Source/WebCore/page/LocalFrameView.h index 05a0597d96f2..404628d05be6 100644 --- a/Source/WebCore/page/LocalFrameView.h +++ b/Source/WebCore/page/LocalFrameView.h @@ -321,8 +321,6 @@ class LocalFrameView final : public FrameView { // These are in document coordinates, unaffected by page scale (but affected by zooming). WEBCORE_EXPORT LayoutRect layoutViewportRect() const final; - void updateLayoutViewportRect(); - void updateContentsSizeForRemoteFrames(); WEBCORE_EXPORT LayoutRect visualViewportRect() const; LayoutRect layoutViewportRectIncludingObscuredInsets() const; diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp index 0e0f0ccc802a..15e2ad8f7b83 100644 --- a/Source/WebCore/page/Page.cpp +++ b/Source/WebCore/page/Page.cpp @@ -2272,9 +2272,6 @@ void Page::syncLocalFrameInfoToRemote() forEachLocalFrame([] (LocalFrame& frame) { RefPtr frameView = frame.view(); - frameView->updateLayoutViewportRect(); - frameView->updateContentsSizeForRemoteFrames(); - HashMap> childrenFrameLayoutInfo; auto windowClipRectInContentCoordinates = [&frameView, rect = std::optional { }]() mutable { if (!rect) @@ -2318,7 +2315,11 @@ void Page::syncLocalFrameInfoToRemote() )); } - frame.loader().client().broadcastChildrenFrameLayoutInfoToOtherProcesses(childrenFrameLayoutInfo); + frame.loader().client().broadcastFrameGeometryToOtherProcesses({ + frameView->layoutViewportRect(), + frameView->contentsSize(), + WTF::move(childrenFrameLayoutInfo) + }); }); } diff --git a/Source/WebCore/page/RemoteFrame.cpp b/Source/WebCore/page/RemoteFrame.cpp index e096e05a6755..8275ac1c834b 100644 --- a/Source/WebCore/page/RemoteFrame.cpp +++ b/Source/WebCore/page/RemoteFrame.cpp @@ -257,7 +257,7 @@ ColorSchemePreference RemoteFrame::colorSchemePreference() const float RemoteFrame::usedZoomForChild(const Frame& child) const { - if (RefPtr info = frameTreeSyncData().childrenFrameLayoutInfo.get(child.frameID())) + if (RefPtr info = frameTreeSyncData().frameGeometry.childrenFrameLayoutInfo.get(child.frameID())) return info->usedZoom(); return 1.0; diff --git a/Source/WebCore/page/RemoteFrameLayoutInfo.cpp b/Source/WebCore/page/RemoteFrameLayoutInfo.cpp index c7ce496ea3c7..96ddbdff64d6 100644 --- a/Source/WebCore/page/RemoteFrameLayoutInfo.cpp +++ b/Source/WebCore/page/RemoteFrameLayoutInfo.cpp @@ -28,6 +28,7 @@ #include "FloatRect.h" #include +#include namespace WebCore { @@ -78,4 +79,35 @@ std::optional RemoteFrameLayoutInfo::mapParentContentsToChildWindow(c return ownerLocal; } +WTF::TextStream& operator<<(WTF::TextStream& ts, FrameOwnerElementAppearance appearance) +{ + switch (appearance) { + case FrameOwnerElementAppearance::IsDark: + ts << "IsDark"_s; + break; + case FrameOwnerElementAppearance::ExplicitlySet: + ts << "ExplicitlySet"_s; + break; + } + return ts; +} + +WTF::TextStream& operator<<(WTF::TextStream& ts, const RemoteFrameLayoutInfo& info) +{ + WTF::TextStream::GroupScope scope(ts); + ts << "RemoteFrameLayoutInfo"_s; + ts.dumpProperty("windowClipRectInParent"_s, info.windowClipRectInParent()); + ts.dumpProperty("visibleRectInParent"_s, info.visibleRectInParent()); +#if PLATFORM(IOS_FAMILY) + ts.dumpProperty("exposedContentRectInParent"_s, info.exposedContentRectInParent()); +#endif + ts.dumpProperty("ownerHasRenderer"_s, info.ownerHasRenderer()); + ts.dumpProperty("childFrameOwnerToRootContentTransform"_s, info.childFrameOwnerToRootContentTransform()); + ts.dumpProperty("absoluteToChildFrameOwnerLocalTransform"_s, info.absoluteToChildFrameOwnerLocalTransform()); + ts.dumpProperty("usedZoom"_s, info.usedZoom()); + ts.dumpProperty("contentBoxLocation"_s, info.contentBoxLocation()); + ts.dumpProperty("ownerElementAppearance"_s, info.ownerElementAppearance()); + return ts; +} + } // namespace WebCore diff --git a/Source/WebCore/page/RemoteFrameLayoutInfo.h b/Source/WebCore/page/RemoteFrameLayoutInfo.h index b6938a251757..25eaf57fb0ea 100644 --- a/Source/WebCore/page/RemoteFrameLayoutInfo.h +++ b/Source/WebCore/page/RemoteFrameLayoutInfo.h @@ -120,4 +120,7 @@ class RemoteFrameLayoutInfo : public RefCounted { OptionSet m_ownerElementAppearance; }; +WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, FrameOwnerElementAppearance); +WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, const RemoteFrameLayoutInfo&); + }; diff --git a/Source/WebCore/page/RemoteFrameView.cpp b/Source/WebCore/page/RemoteFrameView.cpp index 1d71a251348b..2d19cdc484f2 100644 --- a/Source/WebCore/page/RemoteFrameView.cpp +++ b/Source/WebCore/page/RemoteFrameView.cpp @@ -74,17 +74,17 @@ void RemoteFrameView::setFrameRect(const IntRect& newRect) LayoutRect RemoteFrameView::layoutViewportRect() const { - return m_frame->frameTreeSyncData().frameLayoutViewportRect; + return m_frame->frameTreeSyncData().frameGeometry.layoutViewportRect; } IntSize RemoteFrameView::contentsSize() const { - return m_frame->frameTreeSyncData().frameContentsSize; + return m_frame->frameTreeSyncData().frameGeometry.contentsSize; } std::optional RemoteFrameView::visibleRectOfChild(const Frame& child) const { - if (RefPtr info = m_frame->frameTreeSyncData().childrenFrameLayoutInfo.get(child.frameID())) + if (RefPtr info = m_frame->frameTreeSyncData().frameGeometry.childrenFrameLayoutInfo.get(child.frameID())) return info->visibleRectInParent(); return std::nullopt; @@ -92,7 +92,7 @@ std::optional RemoteFrameView::visibleRectOfChild(const Frame& child OptionSet RemoteFrameView::appearanceOfOwnerElementOfChildFrame(const Frame& child) const { - if (RefPtr info = m_frame->frameTreeSyncData().childrenFrameLayoutInfo.get(child.frameID())) + if (RefPtr info = m_frame->frameTreeSyncData().frameGeometry.childrenFrameLayoutInfo.get(child.frameID())) return info->ownerElementAppearance(); return { }; @@ -100,7 +100,7 @@ OptionSet RemoteFrameView::appearanceOfOwnerElement LayoutPoint RemoteFrameView::childFrameOwnerContentBoxLocation(const Frame& child) const { - if (RefPtr info = m_frame->frameTreeSyncData().childrenFrameLayoutInfo.get(child.frameID())) + if (RefPtr info = m_frame->frameTreeSyncData().frameGeometry.childrenFrameLayoutInfo.get(child.frameID())) return info->contentBoxLocation(); return { }; @@ -108,7 +108,7 @@ LayoutPoint RemoteFrameView::childFrameOwnerContentBoxLocation(const Frame& chil TransformationMatrix RemoteFrameView::childFrameOwnerToRootContentTransform(const Frame& child) const { - if (RefPtr info = m_frame->frameTreeSyncData().childrenFrameLayoutInfo.get(child.frameID())) + if (RefPtr info = m_frame->frameTreeSyncData().frameGeometry.childrenFrameLayoutInfo.get(child.frameID())) return info->childFrameOwnerToRootContentTransform(); return { }; @@ -116,7 +116,7 @@ TransformationMatrix RemoteFrameView::childFrameOwnerToRootContentTransform(cons TransformationMatrix RemoteFrameView::absoluteToChildFrameOwnerLocalTransform(const Frame& child) const { - if (RefPtr info = m_frame->frameTreeSyncData().childrenFrameLayoutInfo.get(child.frameID())) + if (RefPtr info = m_frame->frameTreeSyncData().frameGeometry.childrenFrameLayoutInfo.get(child.frameID())) return info->absoluteToChildFrameOwnerLocalTransform(); return { }; diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in index 6fdb08fa4508..7114f8403377 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in @@ -1349,6 +1349,13 @@ using WebCore::ScrollPosition = WebCore::IntPoint; OptionSet ownerElementAppearance(); }; +header: +struct WebCore::FrameGeometrySyncData { + WebCore::LayoutRect layoutViewportRect; + WebCore::IntSize contentsSize; + HashMap> childrenFrameLayoutInfo; +}; + header: [CustomHeader] struct WebCore::ScreenDataOverrides { double width; diff --git a/Source/WebKit/UIProcess/WebFrameProxy.cpp b/Source/WebKit/UIProcess/WebFrameProxy.cpp index 8f42f21e992c..3be71d95b523 100644 --- a/Source/WebKit/UIProcess/WebFrameProxy.cpp +++ b/Source/WebKit/UIProcess/WebFrameProxy.cpp @@ -905,7 +905,7 @@ Ref WebFrameProxy::calculateFrameTreeSyncData() const bool isSecureForPaymentSession = false; #endif - return FrameTreeSyncData::create(isSecureForPaymentSession, securityOrigin(), m_documentSecurityPolicy, m_effectiveSandboxFlags.contains(WebCore::SandboxFlag::Origin), url().protocol().toString(), IntRect { }, ScrollPosition { }, LayoutRect { }, IntSize { }, HashMap> { }); + return FrameTreeSyncData::create(isSecureForPaymentSession, securityOrigin(), m_documentSecurityPolicy, m_effectiveSandboxFlags.contains(WebCore::SandboxFlag::Origin), url().protocol().toString(), IntRect { }, ScrollPosition { }, FrameGeometrySyncData { }); } Ref WebFrameProxy::securityOrigin() const diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp index d3d03ad804e8..2e20920cbc23 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp @@ -1453,7 +1453,7 @@ void WebPage::frameTreeSyncDataChangedInAnotherProcess(FrameIdentifier frameID, view->scrollTo(coreFrame->frameTreeSyncData().frameScrollPosition); break; - case FrameTreeSyncDataType::ChildrenFrameLayoutInfo: + case FrameTreeSyncDataType::FrameGeometry: updateChildFrameVisibleRectsFromParent(*coreFrame); break; @@ -1465,8 +1465,7 @@ void WebPage::frameTreeSyncDataChangedInAnotherProcess(FrameIdentifier frameID, switch (dataType) { case FrameTreeSyncDataType::FrameRect: case FrameTreeSyncDataType::FrameScrollPosition: - case FrameTreeSyncDataType::FrameLayoutViewportRect: - case FrameTreeSyncDataType::ChildrenFrameLayoutInfo: + case FrameTreeSyncDataType::FrameGeometry: updatePDFHUDLocationsAfterRemoteFrameGeometryChange(); break; default: @@ -1495,7 +1494,7 @@ void WebPage::updateChildFrameVisibleRectsFromParent(WebCore::Frame& parentCoreF if (!m_page || !m_page->settings().siteIsolationEnabled()) return; - auto& childrenInfo = parentCoreFrame.frameTreeSyncData().childrenFrameLayoutInfo; + auto& childrenInfo = parentCoreFrame.frameTreeSyncData().frameGeometry.childrenFrameLayoutInfo; if (childrenInfo.isEmpty()) return; From 845882297cd22aeb22135aecf95aebbe4638842a Mon Sep 17 00:00:00 2001 From: Alan Baradlay Date: Fri, 28 Aug 2026 08:22:35 -0700 Subject: [PATCH 036/103] [css-text-decor] Negative text-decoration-inset is clipped when the decoration propagates to a descendant inline box https://bugs.webkit.org/show_bug.cgi?id=322718 Reviewed by Antti Koivisto. collectInkOverflowForTextDecorations() measured each text box's decoration overflow from displayBox.layoutBox().parent().style(). The decoration properties are not inherited, so when a decoration propagates from an ancestor to a descendant inline box that immediate parent carries the initial values, and the overflow came out too small. The decoration was still painted in the right place - TextBoxPainter walks up to the originating box - so it got clipped to the undersized overflow instead. Test: imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow.html * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow-expected.html: Added. * Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp: (WebCore::Layout::InlineDisplayContentBuilder::collectInkOverflowForTextDecorations): Canonical link: https://commits.webkit.org/320044@main --- ...tion-propagated-ink-overflow-expected.html | 16 ++++++++ ...xt-decoration-propagated-ink-overflow.html | 23 +++++++++++ .../display/InlineDisplayContentBuilder.cpp | 40 +++++++++++++------ 3 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow-expected.html new file mode 100644 index 000000000000..6fbe17892ee5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow-expected.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration: a decoration propagated to a descendant inline box is measured with the originating box's style (reference) + + + + +
thickness
+
wavyline
+
negative inset
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow.html new file mode 100644 index 000000000000..e13bd3f57265 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-propagated-ink-overflow.html @@ -0,0 +1,23 @@ + + +CSS Text Decoration: a decoration propagated to a descendant inline box is measured with the originating box's style + + + + + + + +
thickness
+
wavyline
+
negative inset
diff --git a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp index 76a03fb2c5ac..a61697043396 100644 --- a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp +++ b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp @@ -1223,20 +1223,34 @@ void InlineDisplayContentBuilder::collectInkOverflowForTextDecorations(std::span if (!displayBox.isText()) continue; - CheckedRef parentStyle = displayBox.layoutBox().parent().style(); - auto textDecorations = parentStyle->textDecorationLineInEffect(); - if (!textDecorations) - continue; - + // Note that decoration properties are not inherited but propagated auto decorationOverflow = [&] { - if (!textDecorations.hasUnderline()) - return inkOverflowForDecorations(parentStyle); - - if (!logicalBottomForTextDecoration) - logicalBottomForTextDecoration = logicalBottomForTextDecorationContent(boxes, isHorizontalWritingMode); - auto textRunLogicalOffsetFromLineBottom = *logicalBottomForTextDecoration - (isHorizontalWritingMode ? displayBox.bottom() : displayBox.right()); - auto textRunLogicalHeight = isHorizontalWritingMode ? displayBox.height() : displayBox.width(); - return inkOverflowForDecorations(parentStyle, { textRunLogicalHeight, textRunLogicalOffsetFromLineBottom }); + auto overflowForDecoratingBox = [&](auto& decoratingBoxStyle) { + if (!decoratingBoxStyle.textDecorationLineInEffect().hasUnderline()) + return inkOverflowForDecorations(decoratingBoxStyle); + + if (!logicalBottomForTextDecoration) + logicalBottomForTextDecoration = logicalBottomForTextDecorationContent(boxes, isHorizontalWritingMode); + auto textRunLogicalOffsetFromLineBottom = *logicalBottomForTextDecoration - (isHorizontalWritingMode ? displayBox.bottom() : displayBox.right()); + auto textRunLogicalHeight = isHorizontalWritingMode ? displayBox.height() : displayBox.width(); + return inkOverflowForDecorations(decoratingBoxStyle, { textRunLogicalHeight, textRunLogicalOffsetFromLineBottom }); + }; + + // Several ancestors may each decorate this text box, which then has to accommodate whichever of them overflows it the most on each side. + auto maximumOverflow = InkOverflowForDecorations { }; + for (CheckedPtr box = &displayBox.layoutBox().parent(); box; box = &box->parent()) { + CheckedRef style = isFirstFormattedLine() ? box->firstLineStyle() : box->style(); + if (style->textDecorationLine()) { + auto overflow = overflowForDecoratingBox(style.get()); + maximumOverflow.top() = std::max(maximumOverflow.top(), overflow.top()); + maximumOverflow.right() = std::max(maximumOverflow.right(), overflow.right()); + maximumOverflow.bottom() = std::max(maximumOverflow.bottom(), overflow.bottom()); + maximumOverflow.left() = std::max(maximumOverflow.left(), overflow.left()); + } + if (box == &root()) + break; + } + return maximumOverflow; }(); if (!decorationOverflow.isZero()) { From 1aa99d7546d2e0a6a3efb1fa652cfa450ebc45ff Mon Sep 17 00:00:00 2001 From: Nikolas Zimmermann Date: Fri, 28 Aug 2026 08:30:27 -0700 Subject: [PATCH 037/103] [LBSE] Fix viewport clipping issues https://bugs.webkit.org/show_bug.cgi?id=322219 Reviewed by Simon Fraser. A nested and a clip their content to their viewport, and we apply that clip even when the whole content is already inside it. Nothing pixel-snaps the clip for them, because rendererNeedsPixelSnapping() opts every SVG renderer below the outermost out. The viewport border can therefore fall between two device pixels, and content drawn exactly on it loses part of its outer edge. In svg/custom/viewbox-syntax.svg that is the red stroke around each nested . A marker viewport does the same to its content, in painting-marker-03-f, marker-default-width-height, shapes-supporting-markers and the two js-late-marker tests. All of them have subtle pixel test failures, compared to the legacy SVG engine. The fix is to not apply a clip that removes nothing, but just causes undesired side effects. To achieve this the existing overflow clip handling in LBSE was overhauled: overflowClipRect() keeps returning the rectangle itself, the way RenderBox does, for whoever needs to intersect a rectangle against it, currently SVGBoundingBoxComputation::handleRootOrContainer() and RenderSVGModelObject::updateCachedVisualOverflowRect(). The painting paths now query overflowClipRectForPainting() instead, which returns the infinite rectangle as long as the content stays inside the viewport, and overflowClipRect() otherwise. Rebaseline a few tests that gain the restored edge or no longer show clip annotations in the render tree dumps, covered by existing tests. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/coords-viewattr-01-b-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-comptran-01-b-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-example-01-b-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/painting-marker-03-f-expected.png: Added. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/types-basicDOM-01-b-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/container-opacity-clip-viewBox-expected.png: Added. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/container-opacity-clip-viewBox-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/feComponentTransfer-Discrete-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/feComponentTransfer-Gamma-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/feComponentTransfer-Linear-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/feComponentTransfer-Table-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/js-late-marker-and-object-creation-expected.png: Added. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/js-late-marker-creation-expected.png: Added. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/marker-default-width-height-expected.png: Added. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/preserve-aspect-ratio-syntax-expected.png: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/preserve-aspect-ratio-syntax-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-inner-svg-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-on-symbol-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-without-attributes-on-symbol-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/root-container-opacity-clip-viewBox-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/shapes-supporting-markers-expected.png: Added. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/text-rotated-gradient-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-in-symbol-with-offset-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg1-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg2-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol1-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol2-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.png: Added. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/text/text-viewbox-rescale-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.png: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.txt: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/text/zoom-coords-viewattr-01-b-expected.png: * LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/text/zoom-coords-viewattr-01-b-expected.txt: * Source/WebCore/rendering/RenderLayer.cpp: (WebCore::RenderLayer::calculateClipRects const): * Source/WebCore/rendering/RenderLayer.h: * Source/WebCore/rendering/RenderLayerSVGAdditions.cpp: (WebCore::RenderLayer::appendChildrenInDOMOrderForSVG): * Source/WebCore/rendering/svg/RenderSVGContainer.cpp: (WebCore::RenderSVGContainer::paint): * Source/WebCore/rendering/svg/RenderSVGModelObject.cpp: (WebCore::RenderSVGModelObject::requiresLayer const): (WebCore::RenderSVGModelObject::overflowClipRectForPainting const): * Source/WebCore/rendering/svg/RenderSVGModelObject.h: (WebCore::RenderSVGModelObject::locationOffsetEquivalent const): (WebCore::RenderSVGModelObject::visualOverflowRectEquivalent const): (WebCore::RenderSVGModelObject::cachedVisualOverflowRectIfAvailable const): (WebCore::RenderSVGModelObject::updateCachedVisualOverflowRect const): (WebCore::RenderSVGModelObject::overflowClipRectForChildLayers): (WebCore::RenderSVGModelObject::updateCachedVisualOverflowRect): Deleted. * Source/WebCore/rendering/svg/RenderSVGModelObjectInlines.h: (WebCore::RenderSVGModelObject::clipsSubtree): * Source/WebCore/rendering/svg/SVGBoundingBoxComputation.cpp: (WebCore::SVGBoundingBoxComputation::handleRootOrContainer const): (WebCore::computeVisualOverflowRectWithOptions): (WebCore::SVGBoundingBoxComputation::computeVisualOverflowRect): (WebCore::SVGBoundingBoxComputation::computeVisualOverflowRectIgnoringViewportClip): * Source/WebCore/rendering/svg/SVGBoundingBoxComputation.h: Canonical link: https://commits.webkit.org/320045@main --- .../coords-viewattr-01-b-expected.txt | 22 ++++---- .../filters-comptran-01-b-expected.txt | 8 +-- .../filters-example-01-b-expected.txt | 4 +- .../painting-marker-03-f-expected.png | Bin 0 -> 38087 bytes .../types-basicDOM-01-b-expected.txt | 2 +- ...ontainer-opacity-clip-viewBox-expected.png | Bin 0 -> 21265 bytes ...ontainer-opacity-clip-viewBox-expected.txt | 2 +- .../feComponentTransfer-Discrete-expected.txt | 8 +-- .../feComponentTransfer-Gamma-expected.txt | 8 +-- .../feComponentTransfer-Linear-expected.txt | 8 +-- .../feComponentTransfer-Table-expected.txt | 8 +-- ...te-marker-and-object-creation-expected.png | Bin 0 -> 11484 bytes .../js-late-marker-creation-expected.png | Bin 0 -> 11484 bytes .../marker-default-width-height-expected.png | Bin 0 -> 39595 bytes .../preserve-aspect-ratio-syntax-expected.png | Bin 31108 -> 31120 bytes .../preserve-aspect-ratio-syntax-expected.txt | 40 +++++++-------- .../relative-sized-inner-svg-expected.txt | 2 +- .../relative-sized-use-on-symbol-expected.txt | 2 +- ...-without-attributes-on-symbol-expected.txt | 2 +- ...ontainer-opacity-clip-viewBox-expected.txt | 2 +- .../shapes-supporting-markers-expected.png | Bin 0 -> 34686 bytes .../custom/text-rotated-gradient-expected.txt | 2 +- .../use-in-symbol-with-offset-expected.txt | 2 +- ...idth-height-properties-to-svg-expected.txt | 6 +-- ...dth-height-properties-to-svg1-expected.txt | 4 +- ...dth-height-properties-to-svg2-expected.txt | 4 +- ...h-height-properties-to-symbol-expected.txt | 2 +- ...-height-properties-to-symbol1-expected.txt | 2 +- ...-height-properties-to-symbol2-expected.txt | 4 +- .../svg/custom/viewbox-syntax-expected.png | Bin 0 -> 33565 bytes .../svg/custom/viewbox-syntax-expected.txt | 48 +++++++++--------- .../text/text-viewbox-rescale-expected.txt | 4 +- .../zoom-coords-viewattr-01-b-expected.png | Bin 39392 -> 39429 bytes .../zoom-coords-viewattr-01-b-expected.txt | 22 ++++---- .../zoom-coords-viewattr-01-b-expected.png | Bin 50567 -> 50636 bytes .../zoom-coords-viewattr-01-b-expected.txt | 22 ++++---- Source/WebCore/rendering/RenderLayer.cpp | 29 ++++++----- Source/WebCore/rendering/RenderLayer.h | 4 +- .../rendering/RenderLayerSVGAdditions.cpp | 9 ++-- .../rendering/svg/RenderSVGContainer.cpp | 6 ++- .../rendering/svg/RenderSVGModelObject.cpp | 19 ++++++- .../rendering/svg/RenderSVGModelObject.h | 35 ++++++++++--- .../svg/RenderSVGModelObjectInlines.h | 6 +++ .../svg/SVGBoundingBoxComputation.cpp | 30 +++++++---- .../rendering/svg/SVGBoundingBoxComputation.h | 4 +- 45 files changed, 222 insertions(+), 160 deletions(-) create mode 100644 LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/painting-marker-03-f-expected.png create mode 100644 LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/container-opacity-clip-viewBox-expected.png create mode 100644 LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/js-late-marker-and-object-creation-expected.png create mode 100644 LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/js-late-marker-creation-expected.png create mode 100644 LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/marker-default-width-height-expected.png create mode 100644 LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/shapes-supporting-markers-expected.png create mode 100644 LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.png diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/coords-viewattr-01-b-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/coords-viewattr-01-b-expected.txt index d0c3cf2cd29f..e93786598c2b 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/coords-viewattr-01-b-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/coords-viewattr-01-b-expected.txt @@ -48,7 +48,7 @@ layer at (0,-27.75) size 112x58 backgroundClip at (0,0) size 480x360 clip at (0, layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 RenderSVGViewportContainer {svg} at (0,12.75) size 50x30 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (120,80) size 50x30 clip at (120,80) size 50x30 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -60,10 +60,10 @@ layer at (0,-12.75) size 50x43 backgroundClip at (0,0) size 480x360 clip at (0,0 RenderSVGInlineText {#text} at (0,0) size 25x10 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 24.00: "xMid*" RenderSVGRect {rect} at (0.50,13.25) size 49x29 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=49.00] [height=29.00] -layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 clip at (0,0) size 48.34x40 +layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 RenderSVGViewportContainer {svg} at (0,12.75) size 50x30 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (189.98,80) size 50.02x30 clip at (189.98,80) size 50.02x30 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -75,10 +75,10 @@ layer at (0,-12.75) size 50x43 backgroundClip at (0,0) size 480x360 clip at (0,0 RenderSVGInlineText {#text} at (0,0) size 26x10 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 25.50: "xMax*" RenderSVGRect {rect} at (0.50,13.25) size 49x29 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=49.00] [height=29.00] -layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 clip at (0,0) size 30.02x40 +layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 RenderSVGViewportContainer {svg} at (0,12.75) size 50x30 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (119.98,130) size 50.02x30 clip at (119.98,130) size 50.02x30 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -94,10 +94,10 @@ layer at (0,-27.75) size 124x88 backgroundClip at (0,0) size 480x360 clip at (0, RenderSVGInlineText {#text} at (0,0) size 27x10 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 26.00: "*YMin" RenderSVGRect {rect} at (0.50,13.25) size 29x59 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=29.00] [height=59.00] -layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 clip at (0,0) size 30x60 +layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,12.75) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (300,80) size 30x60 clip at (300,80) size 30x60 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -109,10 +109,10 @@ layer at (0,-12.75) size 30x73 backgroundClip at (0,0) size 480x360 clip at (0,0 RenderSVGInlineText {#text} at (0,0) size 27x10 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 26.00: "*YMid" RenderSVGRect {rect} at (0.50,13.25) size 29x59 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=29.00] [height=59.00] -layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,12.75) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (350,80) size 30x60 clip at (350,80) size 30x60 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -124,10 +124,10 @@ layer at (0,-12.75) size 30x73 backgroundClip at (0,0) size 480x360 clip at (0,0 RenderSVGInlineText {#text} at (0,0) size 28x10 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 27.50: "*YMax" RenderSVGRect {rect} at (0.50,13.25) size 29x59 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=29.00] [height=59.00] -layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,12.75) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (400,80) size 30x60 clip at (400,80) size 30x60 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-comptran-01-b-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-comptran-01-b-expected.txt index 2da0055fa2f5..0c07f7048da8 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-comptran-01-b-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-comptran-01-b-expected.txt @@ -40,11 +40,11 @@ layer at (0,0) size 450x300 backgroundClip at (0.50,0.50) size 479.50x359.50 RenderSVGText {text} at (9,348) size 586x38 contains 1 chunk(s) RenderSVGInlineText {#text} at (0,0) size 586x37 chunk 1 text run 1 at (10.00,380.00) startOffset 0 endOffset 34 width 585.21: "type: gamma ampl:2 exponents:5/3/1" -layer at (10,10) size 580x40 backgroundClip at (15,5) size 450x300 clip at (15,5) size 450x300 +layer at (10,10) size 580x40 backgroundClip at (0,0) size 480x360 clip at (0,0) size 480x360 RenderSVGRect {rect} at (9,9) size 580x40 [fill={[type=SOLID] [color=#00000000]}] [x=10.00] [y=10.00] [width=580.00] [height=40.00] -layer at (10,110) size 580x40 backgroundClip at (15,5) size 450x300 clip at (15,5) size 450x300 +layer at (10,110) size 580x40 backgroundClip at (0,0) size 480x360 clip at (0,0) size 480x360 RenderSVGRect {rect} at (9,109) size 580x40 [fill={[type=SOLID] [color=#00000000]}] [x=10.00] [y=110.00] [width=580.00] [height=40.00] -layer at (10,210) size 580x40 backgroundClip at (15,5) size 450x300 clip at (15,5) size 450x300 +layer at (10,210) size 580x40 backgroundClip at (0,0) size 480x360 clip at (0,0) size 480x360 RenderSVGRect {rect} at (9,209) size 580x40 [fill={[type=SOLID] [color=#00000000]}] [x=10.00] [y=210.00] [width=580.00] [height=40.00] -layer at (10,310) size 580x40 backgroundClip at (15,5) size 450x300 clip at (15,5) size 450x300 +layer at (10,310) size 580x40 backgroundClip at (0,0) size 480x360 clip at (0,0) size 480x360 RenderSVGRect {rect} at (9,309) size 580x40 [fill={[type=SOLID] [color=#00000000]}] [x=10.00] [y=310.00] [width=580.00] [height=40.00] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-example-01-b-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-example-01-b-expected.txt index cb96b43067bc..1fa986e85da7 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-example-01-b-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/filters-example-01-b-expected.txt @@ -9,7 +9,7 @@ layer at (0,0) size 480x360 RenderSVGInlineText {#text} at (0,0) size 264x46 chunk 1 text run 1 at (10.00,340.00) startOffset 0 endOffset 16 width 263.34: "$Revision: 1.7 $" RenderSVGRect {rect} at (1,1) size 478x358 [stroke={[type=SOLID] [color=#000000]}] [x=1.00] [y=1.00] [width=478.00] [height=358.00] -layer at (0,0) size 300x180 clip at (0,0) size 200x120 +layer at (0,0) size 300x180 RenderSVGViewportContainer {svg} at (0,0) size 300x180 RenderSVGHiddenContainer {defs} at (0,0) size 0x0 RenderSVGHiddenContainer {filter} at (0,0) size 0x0 @@ -17,7 +17,7 @@ layer at (0,0) size 300x180 clip at (0,0) size 200x120 RenderSVGHiddenContainer {feOffset} at (0,0) size 0x0 RenderSVGHiddenContainer {feComposite} at (0,0) size 0x0 RenderSVGRect {rect} at (1,1) size 198x118 [stroke={[type=SOLID] [color=#0000FF]}] [fill={[type=SOLID] [color=#888888]}] [x=1.00] [y=1.00] [width=198.00] [height=118.00] -layer at (12.50,30) size 176x60 backgroundClip at (80,110) size 300x180 clip at (80,110) size 300x180 +layer at (12.50,30) size 176x60 RenderSVGTransformableContainer {g} at (12.50,30) size 175x60 RenderSVGTransformableContainer {g} at (0,0) size 175x60 RenderSVGPath {path} at (0,0) size 175x60 [stroke={[type=SOLID] [color=#D90000] [stroke width=10.00]}] [data="M 50 90 C 0 90 0 30 50 30 L 150 30 C 200 30 200 90 150 90 Z"] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/painting-marker-03-f-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/painting-marker-03-f-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..37d532da1de4142e0464c443b0152bfc26b85111 GIT binary patch literal 38087 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 z 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%0DgynRVVBb@8qM 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 literal 0 HcmV?d00001 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/types-basicDOM-01-b-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/types-basicDOM-01-b-expected.txt index 824b18fdc2a0..93b658505912 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/types-basicDOM-01-b-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/W3C-SVG-1.1/types-basicDOM-01-b-expected.txt @@ -27,6 +27,6 @@ layer at (0,0) size 402x155 RenderSVGText {text} at (100,111) size 302x24 contains 1 chunk(s) RenderSVGInlineText {#text} at (0,0) size 302x23 chunk 1 text run 1 at (100.00,130.00) startOffset 0 endOffset 35 width 301.10: "Some other text with id 'otherText'" -layer at (0,0) size 110x110 backgroundClip at (0,0) size 50x50 clip at (0,0) size 55x55 +layer at (0,0) size 110x110 backgroundClip at (0,0) size 50x50 RenderSVGViewportContainer {svg} at (0,0) size 110x110 RenderSVGEllipse {circle} at (-50,-50) size 100x100 [fill={[type=SOLID] [color=#FF0000]}] [cx=0.00] [cy=0.00] [r=50.00] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/container-opacity-clip-viewBox-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/container-opacity-clip-viewBox-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..fe5bc5bdd9d54edb680293039e0910ece6fb2ef7 GIT binary patch literal 21265 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 literal 0 HcmV?d00001 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/js-late-marker-creation-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/js-late-marker-creation-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..fd8f12fe416fd8c21e09dce7334a9b9c5aed6054 GIT binary patch literal 11484 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 literal 0 HcmV?d00001 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/marker-default-width-height-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/marker-default-width-height-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f33b1a752c57efc720f4a44cb3109f52118e0f47 GIT binary patch literal 39595 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 CzlwYS literal 31108 zcmeFZXIN8R^EXOSKdoPNJNK+$C2vtN%fY1X3q$4Or5R|G?0@8cvy+{Ov z(0egN2sNSCcjtbd=l|UAd!6&~oDb*RTvy0uv-VoEXU&?KwPt?v_KBV*?d9v2Nk~X& zwH`fqMnXc)LqbAkOhp0QN%0DH1-|ZhXc&9gytcJ*boYdi$V$t|Tic3>S;@T=w|XUQ z{Ypm6T3Y;-l)Rjcm8_VAv<-Au3wVgs{h8){lHz`j72ppiTVt(Py1FEHfom#~3#8Xc zE)tsn9~Dye|G9ohDnLU1*L^Y)k_dZ}3;%AT2YeHM9swWXGynP~`$G1g7UVo%$p3Rq zW=w2aW@6lobZ`Jx8CKhrM1 zKe3e+7mU>i~so*Bu-^!O+#>HzAW(N4cUKw zsa+8xDdYY7otE(nG-_QB*Wdl;L1GszBpoV$d+H6D%~dfHHufsXXMY=kl$_!CzdZw2 z?{r&8UOEl;Kl(2fq!($_*2w=eIY=mnx|4O6K+|e*j1o4@{W?m%fV*^UTUI!KlKVR@+(F*xyM+-}(xqf- zbgbeP^k@YU0Spu~@TI zXo30e_l1hgQ>UX7u>m_-3%y27ZU^|}=7v9h z4S&#KElX?zJ`YojOiL@W8CV*(<754AnXNoFx@>W}YLv?Nd19Y?H%~LHTX_R|wZx)I zb-0MVH_Yru061fyL1oTwv$1^>pg#ZmnNStyPEM!p1xXNAJz+M1Gu;Q% z`LxO6Lg*9(Xg63plF`1nA{MUW@$OubTg}a$%@t}^x zr@HO;E*xUux5Fcbz)YOB-4=!YC_OvEiGGG>iWoI4GPSF7;p5_C0{qt_qezSy(m33*y(dVk>_!M5UOn|ZN zt_KSdl)>Ye>|Drh-g@J+sovG2<`>-}HQh?f;z8+`1V8559-8GJ^*w8fCS%xp znc*>{6-42`#6D+iIzTHkZaH}DNj|D^Rp*aJRa!fQu8H-$|W2;bY+V2+Fj*gH>D~D1p9r%x;9DpY+$s|Ui*Yo_8wT?xT{pL>OUjFu@=SSK$2mYrK(mEiNEu@XIr9o%ZQ zZ!PfJrKMG-iN^BsX01XWGn46`SGkc{e1y!Ev~qJ6b~IwREuhKHmdD4XbYm`Vl9TRJ0k3f zngF5xrulT;rCnvavcPY8iK_~>wLGZFo!*NIj|FjN98Y7zg{#MJu48<*qb<^Y?#%_B zZ+&BF6wuxOqp7OEfJuoUCR-L+fVj1FEW)-dDo%ShmJizE^%n#Z@Nsr0r5UG zxg%98R+8&xh-$4JO}rZGH+MX11*jPFPoenvNAj73G;F9Mpt5eTLuq@^8GDt_2Nb6l z=oX|)V7fFk!{ye0HE%WQ1rCIbprI6vuJU_v<7rDT3!{Ld9 zU+VOqhS#M1O04b2_m9TvtXY=;R%Mdb$HLca>?d*t zD%MI>7Ozpq?3EXm%`R2GNzhl3=e(%0I}CE1D#_n`a$2|B``Iqz^YXyao5s`~QN58b z1As^eULvZ-_9RnhHbC)m=j?6}Wog(BhrfF@6IkyHC4r}oJ`*T&W@F7QBjsSzvE)BS z+(*A6+91yEsn7RI{4s(-Dk$H(l(f2p+=iX>KA9<@(tay=-{5jCrqawj;~aQxZ9?5o zTak*br{F1qm9&1$uV))zuZi2PpC)5Sld8Whqkpg>Bt{^ zskdaSg*n)K0NG(N$A3u9ULOK)xFtU?UX{C1f<<9?c}{l_s+=Nz6R2=e_=>i@U$p61 zT^y7I#euszx>TurT62ffbNJ~3&f?rRWk57wJ6a<_8e%anQ+o2$Vln~L!Uh`Ng>;Dt zRm>f9$&%q-k4h3bhxR>*wm4b*cDVhmxKTbVKW#OMZ~{YDf7tZ{E0(3fS3#MVD@8HZ zjXe_ToF;ZNi_A4WCZfJh?xs=;8lt7&xUKu-!aQ$6hF_W`YocXD^rtHQtBYjyl(suu z*UGwc#V<@z90|c|^3A}fqRE}z*MpF*BH2hpZDfzeTB(LvPqbW~`GJt?TkJeD)WtNw zq@(pIBV{;s2}*x4DmRikz*o>b(V*kYO=bRbpT>KPirsC$p*9K2ee7i*W=d{<*CQbc z1`Q4;mNL*aXsd7vUgv7^ncp9E?iXf+lrf>jDzcS&s5+>F^hL_eV$1092{b`&T)-N8 zcM%q{vI|&Ya=ksF#i+ZDG1LZS>(x`tM85@%^KQg}HKyyIc91$VvdrJ)627~5nIFzf zNv@%!oEOO{(YUNAPNFqXa4tQcc2UF2!f<|Pg`b}bStN=RQPZ?`f?ieXiU2@ zL)AZ;chfrEV_&|uFT)Ha$*>S{5S;v6-W_Coq*R;;YP4af6N4ZA5D)=lIyjc(=7kn7 zXGgM6U!21$!MJ3lG^SI_|EwduB}OFa45I;?dVHtEXVxi&FJsoA;}`e(%4P9=TWR2EZe1itD@V# zj@!x^WKT4d{4mq6DGFeF?_)o8kQyGPon;&|6JoJLEcNTvrbq9Zphb@0 zf^nNzrZ9~Om;UtclR>n@$B|+*l?x^|B&~JCKU1T1znyHBqez_x8X{!#(Ae&PNe45 zp#nJ`eR#a*Gd*4S+oOtrSi}GyK`u{laqw-}*`U-WQvD0^u+PQ3qxE&l#ofqOO<(hx zv@TLO(YGs4E7y${b~oNt%g#Lt<_}pS%cW1hJMPCHVsV_(x$NG;{%IW(wDxo zRsKdObai1l*gBofJ1N`KV3rQSW^m@{a9pSIQzom}!X{Tu`-QA)4KSy2YY&gqk>+P&3H}alu=}M zJ0=ZDq#z)s-d*UuC^@--gzCL@QU3*M4W;sOG2tc7BQL!Xs6kaD!9z(#80$Y;UF77z z-_-3J>bc0TvVF;%8EU8LP{S&eJq9@B$)-ooR+?#Lr%NNpLYcPTr3Q;rmJr-#UYYfN zdp*@vC7cPy(N^OMGquD?OpR4idq+mjdu{g)t%Je{5TsmmW7C5#YgUou2Q3TmJ?ld4 zMY9)sNa(hunMqDzPo}aRR4miHqDFx(cc=lz>vgoFa$aVXTz}j3_f780MJV?L>Ky)` zv77F*{C;MU&nI|0xGv*w)$tG6i9R;Fk=O*=2F18Cq+`_!(&m16%VSgWJ}JknvGG8k z2RHVu)!vN9!Z+MAcN#XDbA3x$Vw9(Tcbh-n8~v~tF4UKR{~%_g&$xTTKwsBV{~>5G zkAYEHGx^b4*@D1eX?|Z$K^ly!(w&B9i0f=ERaH=R(M;FO?;80}4;4{_b}1rCbuYJc z>}~aX!DR-@bNdp<-Q$L00;1V<_+9AnMHaKT4SZKT`MW>$9JgP(2(XM;_rTiL?qQ_; zDju!fS(M!4fOe3X9$6TDi>;{n(D-HT4$80cv+8>KRd|TfgkonoW5{ohM6o$|7%yd_ zV7;410=1~7xI=l5E$ePszJ(17rAqz@N8ela$S{?;>SESW))0S%3z~|x6J(l#C0c|)8rpNZ&)6#Qdu z^Brzk>qt_2-;Yz(4P1_^+QXR=P+d%dbk+f^pN?>QN&?SR|wb67mpnkivbX}BF}RB zqGrS&%}ORsu$y9a5%r3DiQf#487%7o_@Dt#o9_-ptY1%p6}CS8HchE%;{CzSbgtOB zpr8b0yXreznTFjQfe6-88-o;~p^*xb%s=xXLakB|XZhGwUfJgh9)U-_z)o{+_*_%m zd9n+0C?%ZPUMOoUBY$6=O&(lWWCr=m>iGtRflB2r^e&wEFI{!u3xw$bDQOZW8}12O|ZlXwK=Bj;{J7$2Beq9s^spVX1aP8B(r#Q zn@%@*DuLD%?0~m(?fMF(3&Ym8+MYY86#DeOeNPo)cZF2>^iz}gW9!nE%O8RSVyAWU zl>RuNqM2+rD53Nt-#{ApFYzQ0eLxT0OSBtUr@anE<=D4x_`g(>k+T$==w!z8!B;(+GjW-(gCSF3iC&*=7X z!=##!5&vG4Z01K4x~kCa`&C4*c$~vy@M&=|z0n+RU~oUayu&xH!|sA`HXf_1V&66t zNFyMh{%)5X7mZi=CS~HAQZ&xzdf9AtlwdXG6~k%0hWv4d@@4~l-3&Dl3onU>RxO-0eQ#T^nknXi3G1~)mgN(ZDPG$puRvG} zV1VwroGGbR*V35k^?$aFeccqmiv`0us&xvbOlleAl0o1pnZxG&#!X#6>WTU`uA%B- zLy_M5JRm&|d0d^WJZ7;j!~cO>GT6&x6r#P742$&=A#Xhqc$j7U8zzK}Z~T0L-06+n zr19*Z1dG2UsrKDYw2#8WfZ9K0!kcwX>K3fFjUVi~7}<*T=Q%w0_!7c@M0?)!Ztlf7 za;;QwFn!&O14PKUwAEI5=sEq-HG)uyq%jM$h&dkuoBzY3nS>1g=AYqbG*yFJT7jM__w9d) zOy30@)99g-1@6kBbQOGN$8&R#FB>mxcnSXqmipN|@ywBMM>mPgxY~2uBfD}&IB;}_ z1GH)|6E3Se;8mso@#3+@-JH?eNdRk>~D2j;+fikQt&*&UB-_gKK!&YmlB=4d%No4%E<%YgtDb1VSw_UdFKNvr_E$&(YVsPB zx<3_-<>~{%X89-WVh!}>>Q-SKm~w_c?s0 zM38SKgAS!NA*I;5@lIO*}KuSeadJ8C#Rgq z=iJ(77kS8wQHC5~27I`ePS{HFjn2{HuH+$ZDG4OElUX0qH-S@ZX%dA4K zQFNrjvZi0akA};pW!<>@U|S}zv+)ls~%M%wRvZS<$=Y>49&e9djjNKUW!r-egGsE7Kqk_LJimqbj1+Jfd zB*|`|b4Eq;q^jMp5Ytn+70eb>Ub=CkYWKFv08T2SBH_;u{QwDl_OaN+x9IC8=Fs1Z z#(L{tV@HJ%9G7xtVYRl6{fwr1lHcBIK_C{%cCd-f7X#UB&(D?;uTQh`Nak6d>iy~} zGC}3n6_#a1s676rf;mI^jf^!UgFt}`?lESi!mSwrPxJuGTBQU*-Nh_Fx~plkVz>Pw z*~3>0W_}1WZ7#sIkL-^hcJ*I40_izr$Q!X5m9*;?0c|#f8zo)7hq--jwNjq4p*k``$OKK69p5$%4pB zOxl|DM|gV7$i&!xN7)>D0!lEer%-dkq>Yn;t;Xx4pyF@5c(dCnt7Caq>vP6P??5e< zTW*7TO} zcq_2j_F+|rO+c1qYpjZih0OY=MzGl%Hlxw}vqS57vv~|s>SIE*-xB92^Sc#0vqycM z$7TXO7pE}SDCaJ+++I@4*w%ZvQg3$Bqmd77)q_jW30#Q+t$t0eqO~CkNBgwSU z-S=MAy^$_{Gjx2y5`x|Vi_8l0CsoL?KHmLSykl`?orO|oC6`>>SX=f^Bzg02>%{&{9!@mkV92I!t<5JLK;5CckM2yXlxI1X*}dI#l~U~@iiTwxT~d3% zY4Z%-aQM!@)>4Y@;Seq3pZ)Arx89?-yO&Uu%Y~_rrW<%Sd>+YCEiCR=SBqkQ)FnqF z@=ZDBnZ@5|9ZDD34MotN51vgs@$er>z~vCD<3|Y>HBFX$*jSglmgOrPU4V5uB1Dz?4%m)_@I# z(9ja^$?z?3;T?;npmv`9)^n%&rKjV=quWP)m2lhC$C*}#uU~je&?iBw>8H&i&W1;8 zKAUn-A=bGFZxwr^d^(hOx9cFDRgEZH=NJ9;)gN`&6A8=9GCmcjOY%#Xt7om}a@;z>HcI&iymtra$d6gf+{Rbgls#D=~ z*U0ERYpw(Ro7ov3kJ_}EUP6AL5`U~m?!(-Xdo62QgS)el*1of|traZ88d>qx`i=f| zz5%!J=^Hm6W?^cWGb+Si2DIOj38(qJ#1%97qhL3$sW`xX*f+6R|MSx9R9HPWQ8lZg z!tz7PDmWvmRx=Yg+TfWnu@C2#=z|J>a$`$btxjuj%cwmc-n~u6u<=K;w3p;{OyW3!YvVB;U!AnmXf5C@r zIj|>zoC6v$e#nGNV>YeO|^XQ99wBrx^SD**PdA7$w1y0ZY8)HCHXYD z)E)3QW3z|>^zS5^%1Gz~Ju?QB-g}s?83((*>Z1|t&DSDbGVd*TG72&_{5w1M_<#Vr zcM$yk-;98NI{YgS;vXLVOLqPdo4?X?{%=-_2;*qs zCCP1B7>Z$U))b{OWyO5N2e-aHBxL*yu#F=?h&xubFDUssrS6&kM!q=CMZp5oe_$F^wcS#xF%5H*pHqRSOC`w6$;%~*m;bh~ z-;ha@QSe9$H0AtvvA-b`Jlvb8zp^aMo0A)cD!=KsEKQUlH0Zh0|4KO1az=g?Q^H_R?tnng`Eb<Am_me4oK#|ir z5k-*Z$nR-)b-4{BLyWRaXjaVi69dHdZVMxd(~HXLcwK$`bym*Z3pe@GueH@QNxK43 zdWN&3u^e>Yu_8doE=e-sOz6j3+i6UL1?b;W_=W1LoYYw-n-Fd@SC+O@(1I-rDcd9v zHF$PFH+MT0)1O%mipYQDIF?&3R3ic{@h{tK`Wo5V(UWw3pedlT^YzJEEfz04rk8M= z_Bu-aa}w_DMZWQOld|^Ylfg138z-RVY<(T|s2CBfvUDg4rXR;*yW}=of~dU%Sg!)L zH-jVPlzcS5fcWbmrT0iH4=irXjl#F$%A1ZX%}#!mvW=u|k8g{fCDap@3P+*337&H!gcLAV#1r@+m>>X`^`rq z>OSr4_vN-)7^-I#==w&*<0)bB{ICI2-!&U`ADpQ=a;$N*aXD?YY%YKt1swhCpBS3b z4Sx;@C1u8?yuLaaE|XI8+ilh*x;||myW%#RnIlhphXe*zd6L>?X?33!ZFQes(0${I z+~%7JEU*uaGpB#JaiBv3SIG#G z76n%CT#}dk<^Y)1ch=2A#r^yA@= z)Wv1Lp`+->V;}2Z4H^<$Z*>6uFZS9Y-pXm zle-kFY|vn&H?0-fIIOrKhvZxMlZDD0Rkh22TRbsSMQk?`?j#Mv5+UZsm zIW0Yzt;L3K5i&(HmWM$s&Cb4^4R_iLv%y~WB|qe`Ldr#}vdtzZ>EAi4S-o!uxP)c# zReZ?P9^3r&d|l3#b}6m*v~s=i#N@E7Kp2JAJ9bYd1eoR(xFFN$H`m=%8a{%EUT=ZM z7fF%{=X!=k(f(?_-27a;Us`j0pG-7j+$y58B-LDe)6HBLT@=1QW?E7&f_xqlx(e|q z@EWJ{gLfAi56!j@=Qu<_CSu3fom`=t3$T&%MST`ko3qLlp|7a)-(f zTu~v8SixHwU}P}aUotR))Fdst6qOn;$HDr3lPEq9koOLwAqW!~Yia}4< z@_t+coA_9Zb-*eXaJ`GBUPd9RA0*t4j0mj_b^9Ye@A-U;1r4%`x9v7-0emnKu4 z+%amti$yAA0xD_D`Tty7A!>FcZC@atc6v0!TP#3zGwb)~UF7Y@C4~4lp#gT|z6`&r zPDd}?qijBCOp|hAOG?;BHcRS&i+x90OFq?nT!+R|2Q_1E^@>rG_J}1NAk)0}KUfKa z`|E{2TeAY%WkD$YF;NbfgODyrWiaOMdS>hr+Fn4Ndp9k-o{zRdd^4+fsmJE}sst@$ zGGbRs$Oio+Ttx`+RbQvQd@$lROh+A@`&IWCFU3vV?_9s2Kpuxb+)lW=C6)L!woYGN zIrl3K-1tMc)UfUK-d_<|sbQPzy=#iWFRe*L`{pcsLk^^g5eCYVU$e65*9sLyUtE0~Ls?+Uy(t8!5AH)|FgjbZv zi2wB(;ETX(fb>kpObUsSKA8|L>`zpTKcj%jb8EYlP_hcJf@o5DFnK}w{wTP?hLMnot$n?sk$6j;_825HwCC%G>U&<5oJaD((Ic3uyu`x4pI4kDK)|Fkn*IwBw1~EzAmukWSHm`sNEgQaK zx)_ik?w`C8yv8g?i4`6$b7`q zfq28W*BQk}m(Y05fSvbBDOU)&G!-A3jz$>Hc1xBgmA2pBm|iu!!4c>W#DX##Fv20+ zoyWt7RTa*L{f{M;HMRs<1mWrtg{Q+~$@pm6e5wx$Pb04q|B~^Sd1Dt9tz|<)L zJgku7Ckm3o1#6Bc9M{jSgRY-f&&XWc?jDU|l@dCvKb}c#9!s)2l*H$uQehIC_{tG% zV!DEk#*2EHk2x>8R&n(HUz(brU#4G-IjDG+$W97e+fG|~F#z9(3-5C0dgD%gT-`zvQl*bgMFttu~*pWB**JktkBM4N@-W zJ|16$^|uMGKKH&w7NX*e>zy^4%uOSjwb2j20K!DGhWJ0s+C}*JvAW!9UZ+OW?CyQJ zy>SMwIr#Q*R>NZb?|bH$j)qTIq1=H0nNaUWM`+)ug=#1e0tm$_*%8~+O=Bovt`4U1 z>F#<|l-wL`ndfE!-ZB}0IeW;7xj6;h&6=N$Vt$^V zy77bt>hMtLb(&BHT?H@?-?n`$?N%(tR+Qyq@iyocNx+@?h=BO^+ut6xx;qqyc=aS% zxSFaHtJ*xwIHgW^sWU~oeMGh1sRm5QHv$HAg%x#yO5INLZNSaHp9?wRJ=&G5_1T9p znyG_c?Y|R_Fn1L$3XTDuiwy+O?kWz@in=gl zocw;02KPFhUuz+cn2L(4FKyXTBpivyCFFIv`QCo#VL9|Fz(xkp9$ePkB{JG3lML zZj6#wP_EQ{lHhz4?CNigP0cJTqLWIet!ns}lS-16A5X?0lXtw%5_Q1qR2HRuz6gFB z)hFY~l?`6H{?#(2Zu0{_ESd5?c)$o4aAt3u_uAaat47M!apld44QLDL?p){o%qCsu za%xRr^IClhIm7BbaK1ZuZc(|gcehBTyY3D$_xH@;qybLJc;c~IcfN%Xh0D<#7Y5Pv6mY0cwtuc>Oz9YGyolLm0n;OJd0|74|}XeG_l z)G(4;E>HI7RIdR^-i?^!S?_Jn8)j(NA+23B{1EjOId}TdJDaA0;mz-}5K?B(dl$V9 z>llvssy)$z%#}a#g3LtPg@FAA;7gawSiB+$z6}TDY7sVA$1f_IrBs(@Bm~d@i=H6Q z%WC*=>yk!_5Txpf)rwk-qw;F;zU2#w0DoC*MOwGXB{jF1nu%AXl;<5+lV5ak_XwJA zM;+mmn|5-ZD4lJ89rr+E{Q94f1oJb0vINnJAw@2r>Tt@XSb@us_$gcYSYSu=yRJUX zqSDYJg5;KIU0QN=9FF15{EC}-`A$9{)uoRwjEX%{5GwB6j|s%30GpgpWQ|k@H$9DP ztrpexi#Udo!$&LcB=5_e?l1)46IxyTI)W_Z7D7GKZBCTKKWT8tb-xfZOmm%7yvyZf zY>ZYHFZRa(`%llU=xI|3Q(Ka0cpGo(ov+Cwl2a?|?Yy4xp%U_hf8{-N~(5Jh>#dc}# zbYtENZI@mp;?ChOyW8WVou7X&829>Z7V5CB#?e)Nle8=`37Y>g|Fi?#TLa*XwyHV}LsSj_KBu*0y3G2f zuUhJXZA_X43gpW!UlAN?}F+i+B_7su3ba(sggbPPV^Z zKbZ((Makr*0T_Bc8+p6Kkf+JbBMt`Z1pqw#yln-MIXhX2*?oo!mE5_ZM?|98Se%@hO=$83V<-ck}T~^6)#_86vFIb)>*t+JCPiQ#X?vfPF6T0Xt2flO^uv zmq%Jn+l`xW(do5UveW|ruw3(Q7ZE;II-qhK3c#j)7a9ot>?`$fofCBCnbb4@zNXRl z9ZERraem4LhU^Z;21uD5_8QSyoZ*vM;IS<5ItCvl015wy;6Uw_bZzmQtO`CpHdGM2 z@_3=Z;?x0sOY-o{Es6L_V@9gmZ}mgjr{^iELe_~`YXJG~MWqjb*k6^ePu<-m!fzYa zAuf)oFV`|Ty~eHV>s#E80D!REDFB1tzdQhdbnj2~7ZnGqtUiss91FmH>-Y%nL?G%3|S8!2bVu%B2~OoSNB4ve4_}4LL{!@UXne*j1zE zqX!}R>;U$W6#elL7~%=AP}B?nTGHT3n$JE=?60_W0)b>DVi;`YPd5-AstS{s0ZI|j zCj+MgEan5K1dLE%!N~XG5&(jpu>gSJiqfZ&zi0BN_GMn`GPDEkgn>SwG5$UZnTiK1WUDsnm}V1`5I^h_i3u(|q(eaCy@otJqFN%eW3d)yz?zJKk QT@nv=iajiOL9!YaTJB-rKh?U)}|V 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 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/preserve-aspect-ratio-syntax-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/preserve-aspect-ratio-syntax-expected.txt index eaa01be0a030..d8a7e62cb506 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/preserve-aspect-ratio-syntax-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/preserve-aspect-ratio-syntax-expected.txt @@ -14,10 +14,10 @@ layer at (0,0) size 800x600 chunk 1 text run 1 at (0.00,190.00) startOffset 0 endOffset 72 width 264.94: "All svgs below should look the same, all have valid preserveAspectRatio." layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (0,83.33) size 50x99.98 clip at (0,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -25,10 +25,10 @@ layer at (5,5) size 20x20 backgroundClip at (0,83.33) size 50x99.98 clip at (0,8 RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (83.33,83.33) size 49.98x99.98 clip at (83.33,83.33) size 49.98x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -36,10 +36,10 @@ layer at (5,5) size 20x20 backgroundClip at (83.33,83.33) size 49.98x99.98 clip RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (166.66,83.33) size 50x99.98 clip at (166.66,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -47,10 +47,10 @@ layer at (5,5) size 20x20 backgroundClip at (166.66,83.33) size 50x99.98 clip at RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (250,83.33) size 50x99.98 clip at (250,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -58,10 +58,10 @@ layer at (5,5) size 20x20 backgroundClip at (250,83.33) size 50x99.98 clip at (2 RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (333.33,83.33) size 50x99.98 clip at (333.33,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -69,10 +69,10 @@ layer at (5,5) size 20x20 backgroundClip at (333.33,83.33) size 50x99.98 clip at RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (416.66,83.33) size 50x99.98 clip at (416.66,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -80,10 +80,10 @@ layer at (5,5) size 20x20 backgroundClip at (416.66,83.33) size 50x99.98 clip at RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (0,333.33) size 50x100 clip at (0,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -91,10 +91,10 @@ layer at (5,5) size 20x20 backgroundClip at (0,333.33) size 50x100 clip at (0,33 RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (83.33,333.33) size 49.98x100 clip at (83.33,333.33) size 49.98x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -102,10 +102,10 @@ layer at (5,5) size 20x20 backgroundClip at (83.33,333.33) size 49.98x100 clip a RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (166.66,333.33) size 50x100 clip at (166.66,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -113,10 +113,10 @@ layer at (5,5) size 20x20 backgroundClip at (166.66,333.33) size 50x100 clip at RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (250,333.33) size 50x100 clip at (250,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-inner-svg-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-inner-svg-expected.txt index f6d863681b36..bac786cc8bbd 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-inner-svg-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-inner-svg-expected.txt @@ -11,6 +11,6 @@ layer at (0,0) size 800x460 layer at (9,51) size 400x400 RenderSVGRoot {svg} at (1,1) size 400x400 RenderSVGViewportContainer at (0,0) size 400x400 -layer at (9,51) size 400x400 clip at (9,51) size 150x150 +layer at (9,51) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 RenderSVGPath {path} at (7,6.09) size 136x136.09 [fill={[type=SOLID] [color=#008000]}] [data="M 143 103 L 143 117 C 143 130.909 112.555 142.185 75 142.185 C 37.4446 142.185 7.00001 130.909 7.00001 117 L 7 117 L 7 103 C 7 89.0906 37.4446 77.8148 75 77.8148 C 112.555 77.8148 143 89.0906 143 103 L 52 72 C 34.3123 70.6562 20.8513 55.5699 21.5237 37.8439 C 22.1961 20.118 36.7613 6.09483 54.5 6.09483 C 72.2387 6.09483 86.8039 20.118 87.4763 37.8439 C 88.1487 55.5699 74.6877 70.6562 57 72"] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-on-symbol-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-on-symbol-expected.txt index 4b45e92337c7..6343b90ab328 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-on-symbol-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-on-symbol-expected.txt @@ -16,6 +16,6 @@ layer at (9,51) size 400x400 RenderSVGPath {path} at (0,0) size 136x136.09 [fill={[type=SOLID] [color=#008000]}] [data="M 143 103 L 143 117 C 143 130.909 112.555 142.185 75 142.185 C 37.4446 142.185 7.00001 130.909 7.00001 117 L 7 117 L 7 103 C 7 89.0906 37.4446 77.8148 75 77.8148 C 112.555 77.8148 143 89.0906 143 103 L 52 72 C 34.3123 70.6562 20.8513 55.5699 21.5237 37.8439 C 22.1961 20.118 36.7613 6.09483 54.5 6.09483 C 72.2387 6.09483 86.8039 20.118 87.4763 37.8439 C 88.1487 55.5699 74.6877 70.6562 57 72"] layer at (9,51) size 360x360 RenderSVGTransformableContainer {use} at (0,0) size 360x360 -layer at (9,51) size 360x360 clip at (9,51) size 150x150 +layer at (9,51) size 360x360 RenderSVGViewportContainer {svg} at (0,0) size 360x360 RenderSVGPath {path} at (7,6.09) size 136x136.09 [fill={[type=SOLID] [color=#008000]}] [data="M 143 103 L 143 117 C 143 130.909 112.555 142.185 75 142.185 C 37.4446 142.185 7.00001 130.909 7.00001 117 L 7 117 L 7 103 C 7 89.0906 37.4446 77.8148 75 77.8148 C 112.555 77.8148 143 89.0906 143 103 L 52 72 C 34.3123 70.6562 20.8513 55.5699 21.5237 37.8439 C 22.1961 20.118 36.7613 6.09483 54.5 6.09483 C 72.2387 6.09483 86.8039 20.118 87.4763 37.8439 C 88.1487 55.5699 74.6877 70.6562 57 72"] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-without-attributes-on-symbol-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-without-attributes-on-symbol-expected.txt index e308c180ff75..4b2ec7667a54 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-without-attributes-on-symbol-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/relative-sized-use-without-attributes-on-symbol-expected.txt @@ -16,6 +16,6 @@ layer at (9,51) size 400x400 RenderSVGPath {path} at (0,0) size 136x136.09 [fill={[type=SOLID] [color=#008000]}] [data="M 143 103 L 143 117 C 143 130.909 112.555 142.185 75 142.185 C 37.4446 142.185 7.00001 130.909 7.00001 117 L 7 117 L 7 103 C 7 89.0906 37.4446 77.8148 75 77.8148 C 112.555 77.8148 143 89.0906 143 103 L 52 72 C 34.3123 70.6562 20.8513 55.5699 21.5237 37.8439 C 22.1961 20.118 36.7613 6.09483 54.5 6.09483 C 72.2387 6.09483 86.8039 20.118 87.4763 37.8439 C 88.1487 55.5699 74.6877 70.6562 57 72"] layer at (9,51) size 400x400 RenderSVGTransformableContainer {use} at (0,0) size 400x400 -layer at (9,51) size 400x400 clip at (9,51) size 150x150 +layer at (9,51) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 RenderSVGPath {path} at (7,6.09) size 136x136.09 [fill={[type=SOLID] [color=#008000]}] [data="M 143 103 L 143 117 C 143 130.909 112.555 142.185 75 142.185 C 37.4446 142.185 7.00001 130.909 7.00001 117 L 7 117 L 7 103 C 7 89.0906 37.4446 77.8148 75 77.8148 C 112.555 77.8148 143 89.0906 143 103 L 52 72 C 34.3123 70.6562 20.8513 55.5699 21.5237 37.8439 C 22.1961 20.118 36.7613 6.09483 54.5 6.09483 C 72.2387 6.09483 86.8039 20.118 87.4763 37.8439 C 88.1487 55.5699 74.6877 70.6562 57 72"] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/root-container-opacity-clip-viewBox-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/root-container-opacity-clip-viewBox-expected.txt index 8c9369200973..eec376cbe5dd 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/root-container-opacity-clip-viewBox-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/root-container-opacity-clip-viewBox-expected.txt @@ -3,6 +3,6 @@ layer at (0,0) size 800x600 layer at (0,0) size 800x600 RenderSVGRoot {svg} at (0,0) size 800x600 RenderSVGViewportContainer at (0,0) size 800x600 -layer at (0,0) size 200x200 backgroundClip at (0,0) size 83x64 clip at (0,0) size 117x116 +layer at (0,0) size 200x200 backgroundClip at (0,0) size 83x64 RenderSVGViewportContainer {svg} at (0,0) size 200x200 RenderSVGRect {rect} at (-83,-84) size 166x148 [fill={[type=SOLID] [color=#008000]}] [x=-83.00] [y=-84.00] [width=166.00] [height=148.00] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/shapes-supporting-markers-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/shapes-supporting-markers-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..5be91a0e077228cca15d173f312c53278f19d83d GIT binary patch literal 34686 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 literal 0 HcmV?d00001 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/text-rotated-gradient-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/text-rotated-gradient-expected.txt index 86a02d19e779..80aa841ef7e7 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/text-rotated-gradient-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/text-rotated-gradient-expected.txt @@ -35,7 +35,7 @@ layer at (50,21) size 77x37 RenderSVGText {text} at (0,0) size 77x37 contains 1 chunk(s) RenderSVGInlineText {#text} at (0,0) size 77x37 chunk 1 text run 1 at (50.00,50.00) startOffset 0 endOffset 4 width 76.44: "TEST" -layer at (0,0) size 800x600 backgroundClip at (0,121.14) size 374.33x80 clip at (0,0) size 466.67x400 +layer at (0,0) size 800x600 backgroundClip at (0,121.14) size 374.33x80 RenderSVGViewportContainer {svg} at (0,0) size 800x600 layer at (100,71.33) size 77x37 RenderSVGTransformableContainer {g} at (100,71.33) size 76.44x36.67 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-in-symbol-with-offset-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-in-symbol-with-offset-expected.txt index 8a3e9c8090b2..e4c8592323dc 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-in-symbol-with-offset-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-in-symbol-with-offset-expected.txt @@ -19,7 +19,7 @@ layer at (0,0) size 1x2 RenderSVGRect {rect} at (0,0) size 1x2 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=2.00] layer at (0,0) size 100x100 RenderSVGTransformableContainer {use} at (0,0) size 100x100 -layer at (0,0) size 100x100 clip at (0,0) size 2x2 +layer at (0,0) size 100x100 RenderSVGViewportContainer {svg} at (0,0) size 100x100 layer at (0,0) size 1x2 RenderSVGTransformableContainer {g} at (0,0) size 1x2 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg-expected.txt index 9e76ee3dad43..950bc10ec7c6 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg-expected.txt @@ -4,10 +4,10 @@ layer at (0,0) size 400x400 RenderSVGRoot {svg} at (0,0) size 400x400 RenderSVGViewportContainer at (0,0) size 400x400 RenderSVGHiddenContainer {defs} at (0,0) size 400x400 -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 layer at (0,0) size 1x1 RenderSVGTransformableContainer {use} at (0,0) size 1x1 @@ -16,7 +16,7 @@ layer at (0,0) size 1x1 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] layer at (0,0) size 400x400 RenderSVGTransformableContainer {use} at (0,0) size 400x400 -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 layer at (0,0) size 1x1 RenderSVGTransformableContainer {g} at (0,0) size 1x1 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg1-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg1-expected.txt index 5c3a46f895f0..8d0e29e3658c 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg1-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg1-expected.txt @@ -4,11 +4,11 @@ layer at (0,0) size 200x200 RenderSVGRoot {svg} at (0,0) size 200x200 RenderSVGViewportContainer at (0,0) size 200x200 RenderSVGHiddenContainer {defs} at (0,0) size 200x200 -layer at (0,0) size 200x200 clip at (0,0) size 2x2 +layer at (0,0) size 200x200 RenderSVGViewportContainer {svg} at (0,0) size 200x200 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] layer at (0,0) size 200x200 RenderSVGTransformableContainer {use} at (0,0) size 200x200 -layer at (0,0) size 200x200 clip at (0,0) size 2x2 +layer at (0,0) size 200x200 RenderSVGViewportContainer {svg} at (0,0) size 200x200 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg2-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg2-expected.txt index f358622c93d0..1da76fc58468 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg2-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-svg2-expected.txt @@ -5,7 +5,7 @@ layer at (0,0) size 400x400 RenderSVGViewportContainer at (0,0) size 400x400 RenderSVGHiddenContainer {defs} at (0,0) size 400x400 RenderSVGHiddenContainer {symbol} at (0,0) size 1x1 -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] layer at (0,0) size 1x1 @@ -15,7 +15,7 @@ layer at (0,0) size 1x1 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] layer at (0,0) size 400x400 RenderSVGTransformableContainer {use} at (0,0) size 400x400 -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 layer at (0,0) size 1x1 RenderSVGTransformableContainer {g} at (0,0) size 1x1 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol-expected.txt index aca688826ea7..8b727bfce408 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol-expected.txt @@ -14,7 +14,7 @@ layer at (0,0) size 1x1 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] layer at (0,0) size 400x400 RenderSVGTransformableContainer {use} at (0,0) size 400x400 -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 layer at (0,0) size 1x1 RenderSVGTransformableContainer {g} at (0,0) size 1x1 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol1-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol1-expected.txt index 6303b50a7562..a1716da977b5 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol1-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol1-expected.txt @@ -8,6 +8,6 @@ layer at (0,0) size 200x200 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] layer at (0,0) size 200x200 RenderSVGTransformableContainer {use} at (0,0) size 200x200 -layer at (0,0) size 200x200 clip at (0,0) size 2x2 +layer at (0,0) size 200x200 RenderSVGViewportContainer {svg} at (0,0) size 200x200 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol2-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol2-expected.txt index c2546a1549f9..f8d197b4d402 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol2-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/use-transfer-width-height-properties-to-symbol2-expected.txt @@ -6,7 +6,7 @@ layer at (0,0) size 400x400 RenderSVGHiddenContainer {defs} at (0,0) size 400x400 RenderSVGHiddenContainer {symbol} at (0,0) size 1x1 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 layer at (0,0) size 1x1 RenderSVGTransformableContainer {use} at (0,0) size 1x1 @@ -15,7 +15,7 @@ layer at (0,0) size 1x1 RenderSVGRect {rect} at (0,0) size 1x1 [fill={[type=SOLID] [color=#008000]}] [x=0.00] [y=0.00] [width=1.00] [height=1.00] layer at (0,0) size 400x400 RenderSVGTransformableContainer {use} at (0,0) size 400x400 -layer at (0,0) size 400x400 clip at (0,0) size 2x2 +layer at (0,0) size 400x400 RenderSVGViewportContainer {svg} at (0,0) size 400x400 layer at (0,0) size 1x1 RenderSVGTransformableContainer {g} at (0,0) size 1x1 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..99854a5a02ab9de8076daabe687bc8b305d0258d GIT binary patch literal 33565 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 N+HI{{1vei)`#;KaX(Rvu literal 0 HcmV?d00001 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.txt index 98aa3edc9b6e..4b4a0fcfc5d2 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/custom/viewbox-syntax-expected.txt @@ -27,7 +27,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (0,83.33) size 50x99.98 clip at (0,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -38,7 +38,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (83.33,83.33) size 49.98x99.98 clip at (83.33,83.33) size 49.98x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -49,7 +49,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (166.66,83.33) size 50x99.98 clip at (166.66,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -60,7 +60,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (250,83.33) size 50x99.98 clip at (250,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -71,7 +71,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (333.33,83.33) size 50x99.98 clip at (333.33,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -82,7 +82,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (416.66,83.33) size 50x99.98 clip at (416.66,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -93,7 +93,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (500,83.33) size 50x99.98 clip at (500,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -104,7 +104,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (583.33,83.33) size 50x99.98 clip at (583.33,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -115,7 +115,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (666.66,83.33) size 50x99.98 clip at (666.66,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -126,7 +126,7 @@ layer at (0,0) size 30x60 layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (750,83.33) size 50x99.98 clip at (750,83.33) size 50x99.98 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -134,10 +134,10 @@ layer at (5,5) size 20x20 backgroundClip at (750,83.33) size 50x99.98 clip at (7 RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (0,333.33) size 50x100 clip at (0,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -145,10 +145,10 @@ layer at (5,5) size 20x20 backgroundClip at (0,333.33) size 50x100 clip at (0,33 RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (83.33,333.33) size 49.98x100 clip at (83.33,333.33) size 49.98x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -156,10 +156,10 @@ layer at (5,5) size 20x20 backgroundClip at (83.33,333.33) size 49.98x100 clip a RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (166.66,333.33) size 50x100 clip at (166.66,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -167,10 +167,10 @@ layer at (5,5) size 20x20 backgroundClip at (166.66,333.33) size 50x100 clip at RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (250,333.33) size 50x100 clip at (250,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -189,10 +189,10 @@ layer at (5,5) size 20x20 backgroundClip at (333.33,333.33) size 50x100 clip at RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (416.66,333.33) size 50x100 clip at (416.66,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -200,10 +200,10 @@ layer at (5,5) size 20x20 backgroundClip at (416.66,333.33) size 50x100 clip at RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (500,333.33) size 50x100 clip at (500,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -211,10 +211,10 @@ layer at (5,5) size 20x20 backgroundClip at (500,333.33) size 50x100 clip at (50 RenderSVGPath {path} at (5,14) size 10x4 [stroke={[type=SOLID] [color=#000000] [stroke width=2.00]}] [fill={[type=SOLID] [color=#000000]}] [data="M 10 19 L 15 23 L 20 19"] layer at (0,0) size 30x60 RenderSVGTransformableContainer {g} at (0,0) size 30x60 -layer at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,0) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (583.33,333.33) size 50x100 clip at (583.33,333.33) size 50x100 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/text/text-viewbox-rescale-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/text/text-viewbox-rescale-expected.txt index dc47ed468f1c..c040df5fa750 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/text/text-viewbox-rescale-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/text/text-viewbox-rescale-expected.txt @@ -6,7 +6,7 @@ layer at (0,0) size 800x600 layer at (0,0) size 200x200 RenderSVGRoot {svg} at (0,0) size 200x200 RenderSVGViewportContainer at (0,0) size 200x200 -layer at (0,0) size 200x200 clip at (0,0) size 1x1 +layer at (0,0) size 200x200 RenderSVGViewportContainer {svg} at (0,0) size 200x200 RenderSVGText {text} at (0,0) size 1x1 contains 1 chunk(s) RenderSVGInlineText {#text} at (0,0) size 1x1 @@ -15,7 +15,7 @@ layer at (0,0) size 200x200 clip at (0,0) size 1x1 RenderSVGInlineText {#text} at (0,0) size 1x1 chunk 1 text run 1 at (0.56,0.30) startOffset 0 endOffset 4 width 0.24: "PASS" RenderSVGInlineText {#text} at (0,0) size 0x0 -layer at (0,0) size 200x200 clip at (0,0) size 1x1 +layer at (0,0) size 200x200 RenderSVGViewportContainer {svg} at (0,0) size 200x200 RenderSVGText {text} at (0,0) size 1x1 contains 1 chunk(s) RenderSVGInlineText {#text} at (0,0) size 1x1 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.png index 7c5d248bbaf2d402867866ae914b4cf239516099..b77bd28ed05223ff52babc0fe651cb02fb9e37b6 100644 GIT binary patch literal 39429 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 aFYpXynr?}D_7R|g|Ky~UBn!l!d;dR*#$uWP literal 39392 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 diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.txt index c530700eb90c..d1fbfa4c2ea0 100644 --- a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.txt +++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/page/zoom-coords-viewattr-01-b-expected.txt @@ -49,7 +49,7 @@ layer at (0,-28.05) size 112x59 backgroundClip at (0,0) size 1130.39x842.39 clip layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 RenderSVGViewportContainer {svg} at (0,13.05) size 50x30 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (284.39,187.19) size 117x70.19 clip at (284.39,187.19) size 117x70.19 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -61,10 +61,10 @@ layer at (0,-13.05) size 50x44 backgroundClip at (0,0) size 1130.39x842.39 clip RenderSVGInlineText {#text} at (0,0) size 25x11 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 24.00: "xMid*" RenderSVGRect {rect} at (0.50,13.55) size 49x29 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=49.00] [height=29.00] -layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 clip at (0,0) size 48.34x40 +layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 RenderSVGViewportContainer {svg} at (0,13.05) size 50x30 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (448.17,187.19) size 117.03x70.19 clip at (448.17,187.19) size 117.03x70.19 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -76,10 +76,10 @@ layer at (0,-13.05) size 50x44 backgroundClip at (0,0) size 1130.39x842.39 clip RenderSVGInlineText {#text} at (0,0) size 26x11 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 25.50: "xMax*" RenderSVGRect {rect} at (0.50,13.55) size 49x29 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=49.00] [height=29.00] -layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 clip at (0,0) size 30.02x40 +layer at (0,0) size 50x31 backgroundClip at (0,0) size 30x40 RenderSVGViewportContainer {svg} at (0,13.05) size 50x30 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (284.38,304.19) size 117.03x70.19 clip at (284.38,304.19) size 117.03x70.19 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -95,10 +95,10 @@ layer at (0,-28.05) size 124x89 backgroundClip at (0,0) size 1130.39x842.39 clip RenderSVGInlineText {#text} at (0,0) size 27x11 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 26.00: "*YMin" RenderSVGRect {rect} at (0.50,13.55) size 29x59 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=29.00] [height=59.00] -layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 clip at (0,0) size 30x60 +layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,13.05) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (705.59,187.19) size 70.19x140.39 clip at (705.59,187.19) size 70.19x140.39 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -110,10 +110,10 @@ layer at (0,-13.05) size 30x74 backgroundClip at (0,0) size 1130.39x842.39 clip RenderSVGInlineText {#text} at (0,0) size 27x11 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 26.00: "*YMid" RenderSVGRect {rect} at (0.50,13.55) size 29x59 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=29.00] [height=59.00] -layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 clip at (0,0) size 30x50 +layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,13.05) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (822.58,187.19) size 70.19x140.39 clip at (822.58,187.19) size 70.19x140.39 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] @@ -125,10 +125,10 @@ layer at (0,-13.05) size 30x74 backgroundClip at (0,0) size 1130.39x842.39 clip RenderSVGInlineText {#text} at (0,0) size 28x11 chunk 1 text run 1 at (0.00,-5.00) startOffset 0 endOffset 5 width 27.50: "*YMax" RenderSVGRect {rect} at (0.50,13.55) size 29x59 [stroke={[type=SOLID] [color=#0000FF]}] [x=0.50] [y=0.50] [width=29.00] [height=59.00] -layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 clip at (0,0) size 30x40 +layer at (0,0) size 30x61 backgroundClip at (0,0) size 30x60 RenderSVGViewportContainer {svg} at (0,13.05) size 30x60 RenderSVGRect {rect} at (0.50,0.50) size 29x39 [stroke={[type=SOLID] [color=#FF0000]}] [fill={[type=SOLID] [color=#000000]}] [x=0.50] [y=0.50] [width=29.00] [height=39.00] -layer at (5,5) size 20x20 backgroundClip at (939.58,187.19) size 70.19x140.39 clip at (939.58,187.19) size 70.19x140.39 +layer at (5,5) size 20x20 RenderSVGTransformableContainer {g} at (5,5) size 20x20 RenderSVGEllipse {circle} at (0,0) size 20x20 [fill={[type=SOLID] [color=#FFFF00]}] [cx=15.00] [cy=15.00] [r=10.00] RenderSVGEllipse {circle} at (5.50,5.50) size 3x3 [fill={[type=SOLID] [color=#000000]}] [cx=12.00] [cy=12.00] [r=1.50] diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/text/zoom-coords-viewattr-01-b-expected.png b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/zoom/text/zoom-coords-viewattr-01-b-expected.png index e159733c8ea2e1198458480426dd97b22e5145b7..c83ba0fee37090de43622edbaeba478b50f9ef24 100644 GIT binary patch literal 50636 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 Oe8Cl-$QM6A2L2zf99)n9 literal 50567 zcmeFZWn5Hm_dN_FC?SFfq9CEt2uPQNbobChhYSooAPq_xgdiZ@F++(oBPA#xDP2QI z4={As|L~3b_I~|5FMoVKFvH=TIp^%_I{R9C?X`HLsjfgmL`{T)gF~XED65TwbBzH9 z2V_8S9oUlW`N{=&Ve^nP@UVJeW98ruhv8U0<+B#D=H;^%w&t@HwB;8R6ciA)EP~2nQ$J9tZEAebj;1t1m_1`|6m#UqP9mfA_e?ka_Lj_aK9-UCRwE9Do-> z7ezyN930|1SKqie$*K2naHMdQWS{H4#9c?@f29Clwr|rjcxFAyE*uLDMRR(-hL~Hm z=NJT)L!M|~!^Mng=2M|t~+?o;!NW#5COhLl5WgO{C`-J!c_nXlJ@YLlIp zXMnq<5gwQSPA7}JG!8Bvnbf~OvMCH;Rl|;g`1EO_@V9Q_PiX%9E7T-Y?%mykUkdc& zj)+_SCwof%T~9#-3_&>m{^)na{eHjncf6T8FOHV&=5#|!gUj9j{`u;iHBm??EhnGC z7Tup8O%|aXgu}>gnDFjDp9Sn}2*P79WVHU{1VK`|z&5SUFD8FnHCYEa15W3ohu8l6 z#B5;O8|8$>M}M4NDgg&q@h;*0KQEaq9@wV)fk51pqMA*y#I|fnG^#%u&vPAkn7Kb ztqIA3tUtsA{}}}>V4Eycm)q?>E{8z{2-mxin}0^(|3diJMESoE{@*P8&y(_h5Aszg z{x28)m9+oAEhnr#I6J$2k}Ng*hckg1w}jy4BgKY-u!+ir^}zxSjav!=Ppy93q!DsT z#eYsV%x>^!UKe0{YFvJB(#euhd4uj{^IeFC7LB-1Wz)+Un?%TZ$Q||11?fIN)g_Lt zbzHJX)5U#_QUqaV$Ql1bh@{%uNXbTmu~RvvzR9m;hjfmzuLGhUE9M)MH8q`aET>IM zam?d|S~+j4#_hYbJ_FZ6Z|qP~btZ~!x(x6=-yel-f2Yz&i4yvA;p*oP&c`+c z4cc=THv*JyvRaD5exY&x$}?K{YKJc1$imq24O8r(Q{^xD>aB0Yr-w$%55pf=TK#yh z6lDiIB|F50)bjzS8;OCSA6F|0<)h|fr1UMk6fHhbNt772R^t5hu!gf{YT#19rjN?l zkq32tFuBoqime#dGww;Wu6*(9n;_al$1FS325CK4)^za&_uBO_UIQS@Jlv|-? zQ1bH3O7d*$HSBZCbwtYdr=arNnv?m<8G+OKLMBdii)YIw8SwanRa7xFVmZw#{TvwC z>bWqQlk=0)%_jfG#`$EgB%6ftYS`q7D?57V8GBW#uxmCPGt{Zy@@%X5+D1;a#LkSb z|D^yaAuxb1-U9>p;{^{VD4uf*Q&e{1;6~;9$^UG-9coWoOH6Pm?(E|;A1MUV|2CJn z2zo!8Xco|EG&Y3Ld0n@hE+&jA%!!@nFG2MyHJW*j+q`BA{$$tzgyGcXc2N-3B`B;1=J!uY_uq=pkr(T!4C z<(>1h-9Gx8_jwtQ({b^Bvu19-DuT{76)gi%ofY*a0ODTX9B*(v(bNZViLIAvjV5hs zFWuul+G-^|$JUeoY(~G>YCTck1g+)!Zzzb%Y<$c0+xP2sh9%T0T{H>lx{$Wm6&6@A$wFnXlU>Jk1} z)z3_t(Db%cCL1WVEIIqfGXmX4DNjf~vttGE+q2O6S0He?_NKn?W<$;9wlX+LdyuUl z)lvxU5l4E};!5M`3cpp$x|Tb?X&^{UZ_C!t@;x9y#}k$ffbOUtToV^5BpAE*Bhqgk_;` zpX=HgQyDh+9y$%^)j}V5V(XUk<4p={*wH-a^U;!D5)i0Tj4N~!R)m2vqDG+FCP*Ro*j9VD&|WiXPr%zrXGKt4|8(sPnH&b=4Erj4?E(C1 zu3iW@?`?0_GevfLUBZl4Ec>zrxj|M(TXUVa9_o@KQgAV0Ml&^pcbFN)Z2I@0r#vsW z%xAz=jcXRMiBm%&p~?)y2ONB>`Js)`7TUg?@oh2sbl31RbB~Ds3WWyGKcPU^^?`pK zY==3S-pwP4AT|Yg-QX|r9P^xnQ8`u(>)CtH(HuIGnBC}(PY5xzFT*;y&HCgJNgOZ4 zRi3e=zx>v>OkJJ781Ah8Y`d))pY@_-BL2lW)QX&0|6I96ZiTU z_FMPp8&bZZAKwM44|kW1<%K!R2z0*$ag1-xy>tKMUkd<9jonNZ|LSxYwu((f7IR#?WV>T(-qzc!jF?vfWBJP}$qX!ONj(XHj? zRick1oAnzec)~1~`3m2oBy$Z!89>2=Au;iJ zOE7imk}}l86PR*0uyqW|+*bQr1ve>I^3FoRZ2j&ZBosGiSex53;1D>I1c7ra5k}KE4jm zLl7l*40X`ex{G;J)S&?r911O|-x)eRiACB|Xs<7F1*mFB>cf#?35b2fnxgW z@NI#Q;KZCBwNNK7_tDlf0^Q`G30;Ky4e>q}K7>(nqZNRTlxP-v!!E=`fE&zR+`U@q zBea8d!5{0N&;;ib=IXVuD?j!aM&HtDkD_gD@i8MNl&HknPt9wRj>A;hF^`rw+v>&5 zP}QT=OmX=hX=Z_lFz)#D6DIig>R#Qf`p_9~mWArDeHbJaPvFvsjJ?+Bq+22d;dwrc zeIyq4XBi#;ND4=rP2ttXe~JU({;vZ?J#?0#;@uzMNcSzk2;@B=tp0;f2&N`uz(EAR z?&kV~PtaroxQKW+#<5#}l;KdRAe`Lmw|G$z|2?@>9sya9?#~bAVt)_-g}6ZdPc7WB z{^$<^;QyzK)Jtz}dvgZ{0My8d&w-ccqZcP@W%50(ZF#r*%ZGSMxYg31i8$=h2cCV1 zSGW%PPR2TFSH!klZMp9Jv5R%I?hsfX1kM*#fJ1A}fq8cWD8JgNqu0Le^~!;Fg4(0^`Mnh@>2jm0A59ab*$a3 zH;#LE#Mrs&9e1Pf%!{3O3|k9v8WT$?&e-Tt|3h@5`zwhEbpICu-p@$`a<-hXTG=t{N;O@Q=p&36b-;FB$or=>b_dX_l zzJRlrVhb@cj;ncz)u_$JNo9X06(_~eTMMFx2UzsvM3uv`ICsOI_9=j}&PFZ)VDE{B zj~<`Jsf%`I?3UFnQD33Ovl#%MxMLTAWn^)Abs+EE>Vq7Qg%1i71b@9?0)Gl$SK!?G-C)YKE8xYMr7 zB$~J>H5zs}XEy_Dy`@!qR22}e!K-y&Y&PiHmjsVi5}~oZ)j`;xno!l%A{9LYV9PVP zR>umP3~eeCm*uhC16)&p0qR#h++nRh>$TU=nKF^AN1Qa(;1%iy?x}A%{BrL!Gf0|hIh@X?5C*{b z0Ntj5^VsG0E)!eurt^b}I~vb^xuv;(vu9rgKmQIOD0|*tb-Tc9`wT$U3DcKz_>->f zwCCO~4f`Xz*AW?N=;joM(z>sG+3&cZX<}YZ10waYPsS2132RZ#^-NSiOxn`|e}1g@ z9QJQHx+J02WtP%2=EBwg_@Fkj)0F|xQyHeS3uSo!tXCD?ve**JFoo>Zm?h{V78M3apyYWcYa# zyFN8}s(Y`9eGw-RT39IMQGS+t*;(~Wz-co+L$FD_iBiO+n#-`w;6X=x>KxUOW zL7YMY5dZz*7GScj_K9db+m^)?`h>&j+=S{dY5B48(3#4NQeBpn;UfJlpd=}PO#@@J zPU2L*ns41$WJ3m2iDI3H)DgcQq*a5aWtlS#WO%|Yqu(N54fnvn4=zo{Nf65T5@%a7 zgK5l^#2TNTwm6|S1OrFqnaC`R9LvA^?T7ke>)L_(2=#PwI^Yy$=JD)s%AIAG|0z(^ zt&LX{a0~o81!nN@j1s-DOJ!t>t|;5B`Aw|P@Uu@n(}(JEcbDbn)<>h^vm%d|;k2Hgfhpgd@p#n8fzzETs|Wo(wr8flPM;|m04t~}=wCWL$BT)l192`POK>Xe#ZeuxND7aY z{4{y2OS-ZT{CV=_*E$-{3nA5EkWL&j6_{P>%q-_it2M4yB?WxMkOj^wIrYiC(zd@I zlDQn%imIPr&+yxMW2XMF=|+I`1*KVLtg_V9^l=BPQ(5V6aQ0ppc}z$4k{j39un5T% zsp{6aT?Rf-*89!AdcUx=!ZkM+PES`WIv7k$c7{%K$=zwXyf|%!e9n&#dJ%xeC*IsC z*U~pEq?vOnd5tb?AZw3zwO$@>0Q3s!!MGiL)0ZJ~GOC~>RE(%?DN!i>CUOiP zNTLC=6FA3*Tw{b&_;2V(nsn4fm|Upt1)xh$P%2FSv zNb=2rfzU{&(S5~Z-W)Uw!1Jvx0u?C_Q(>Uyx;A>y(ziTu>w4+W{L2!pFk;NvUMXi5 zz1b>|qY$VW_WJalNA-|a>Ok+@&P43ml3 zRrkHYHAxbbj-5T(Iz%ZSZm+z3dclRfr0CN4Rt{J%@h#Q(xTMQTd z@R0yS%Ilkn%M0jkR@k}4!!O(m~cxR4~@>Qv4E@~Qne zrkRl+oC7;|YGVq8F@t?1efD0=%ueB(8o~_rP7CGFxtq?u(0Yzp8q50yodQ+s+J;1Qrc*+EGW>gH&DmQc^QLBsZ>*{DkV17Vg{X|Nl1+_>wL!o@= z_Sb=r?v=fBL;<>QOQyTDb^M!dAO?9%OaLtV+viIqJB4PB5?WkqUJ?veY%mAUY_e|< z#JwHeXW-H=;P2kaV@A4V{@#7MTI!2QJM12~rABjM9=f|_^bA)(YIIxo@&ZFWILxNb z^=$tWSKUHvDX$8bb?=@s>_==YPp%<3|z4H!-txL zpp#BkfC{-gZVN#2ZicLFq!+f(^7xB^D{IXfmw%P}sO@Y`Rn2tYftkm15_ z1N%d}Z=DmKyqGnNDhP+10DTFUMolSZ@|0}OexI+filSiw= ziu{g4sQ^ywA=iAZpsye{W)&?CI&8(FTBqE)IIoTBBsnW$F@`rvqu{eA$)qOn98FyM zCEvW_i-e%Ut4NKb`FSn|kbpK=;aaJrZ$`n7C1^30iy1U}sxiyO<<|;_Gp%CHub1kl zR9@fgnYeE&pZNxa;g?x{n$~3kelqhg45g^TrIvBLls-A-PVe{Hu|$TX0?y?QKRFj2 zCa5)votoO!0Ub&iH92E!^_KUa*1cPq1qippG|k%P$1WSBXRK-A*15Ur97Abk#U^4h zGY>;W9~uG_sDEOoh1aZTU~Too0HgEt!skt*Vy;5MXKu*3866FA%G&T2pLYNb9q}TF ztv6E>QE&)=$Cd4VrA}QF(rcQ;e9i&}VrZY&jr^p6uoTyPAC6(k2TLw5JWIk8AL6_ zj+hqui~cMP^3lJU!6Uu(OStFhQNIZuePqQuOXx(9Y}QZS|LNTp0QIH%-T&Q21Ux{j zx`xbu)Pe*H6hK?MQ~H0l$vyPJ6y{u{9oy!D)@Q1V_sght>2_k6~t+sX@IQ@1NRv z4+ekJ_q^nCZ6o(J?&p0)>P0BBr-XKqKGwL4{bha7eC<~%hjNE~{0?m-=Bu%?iuh8b zLIb77S0pF~;>k1Vs!&l<%?lJWCm(EkcxeQ?*N>Nb=mMwKUz1`o(#xyZK2INNjIlV4 zhU;};4?QNg&uSbd%-@;eWYh1tWqhq7kqMJ^f9gB&xVll?Nl3QhNS+%mAk?0K-HdP1 zSyynFTscm97yzOPp!yXq7C_N6{hPIqvS+$h{z>LUA1X4F^=Izl1qsJbb=?kW>03n``>_&qQZAm0JX+d!QJ%4F(4mF8$6FLy;Tx}hP3-P z`)1gTpu9pIHE~n2=$oC;M4vu~#I2N?{|ZzzS=b)F?>@fG4lN1F#aXaA6!QLZs=sg~ z)2`DVuj>sGeL{Hv=eVb|NCzQKujjvW{ki>!&U)2Qw|C8$i+92@yL8OkCuD0l*6H5e z>d$_yf3oUMFhZ{K(uyUgxkruLrX)cE#U+c?60Oz$;3P0q5ip!D3$eBhK1sB`j^|=y zpUP95ovOQeZ8s)UAiglGC2ONU_9tVNWk>*eC?SphJ+~yk#y}jW_=z038ipJNS=@)g zktvCYKPPl%K=2s6waDk?<*^2Ti-MxS;gqdix;?N`Tih!9gIKw$)yMD|kZ$mu&hKOU zCy805epTrtf`x&iKmMRqf~4j^CRWb1+`tXC+KhR>kyrP~Db&4jk>fR+d4aClmbUPLIS%$NI2- zPkMkT&x5FWN`T=51TVEjZI5sa?V0%S?~&vYb19w$Ejxsia{1{SWVuH6x5A+`iUdO} znVI^}OGQ--;KNfonEQNbVk{R|vpOrh8{3#7C|_5F6!QwF?Y<>3=2+Iyy_Cv#*yy=p zp5H|4r8;F^cQ`FOJiue+rlIjquho?-LJnu38fNhk$)0Cggi)Dva>#G`!Hmzv2y_)4 ztK!EI1<4y$-W%lCw>1AX_h6q`N5yM^F2(F($(*n+`ri`0UIMk%}527Wf9p@w8)!2yp zCP&hxy`a(ln)fAFnM^Z<6pVs8ZQaJcdqoESnnf%r9uglS#e=tpV|ET-jhzmb$9x%P z)(ON|&v!iTVOHby#fnrY(5s#0snd*FK)!O+kufGH;SvxAE8hjiZD&4uk>zZsYsdMT zv8EviUC%_FX6H0qY-sD;5Z?hVljk%ZQOlbBT~yXRV#ML9Q0}B^`Qn#c570zg?_}w* z?e2Kcbt+6;*y7zDHSU|)*H)0hto&=J_pvHFx*xshxj~+h_*8NqzS+X^0$ydGCLG=?o``%hxu0QVEV%|%k1toV?EuAUb(%qwyl+~^>Uj5n*2P*+0#MAA# zCWa@TD>H>oXv*BP?RF%og!ZA@Mr`FxPL(dkhd|rn7|-%{(AjqQ_}1L9XBPi){T(G~ z1`17=@>A>_YEK_5N1%h*>iA$$I)07x69Tp3hhP)0S~R|UyhzpWF5w2RDvBi)pOINj zamJ+YZlk?yWx^PD_AmEG*TVS1tEYIkGl#nUrXOoY1WhnO;NWbBE$Mu2YihVasSR|T z5qQcO5~ZGi$Y3x1C7Y=)zf-+|a!@-tYJYI`SK2SypA{wtWd+euCvva03#lSnk)D+K z@KFI{tH>?LK~X~44VPG?48zQ_T-v={J(PB<8I!<~n*R|(`8h`AIKgb@*?XDU+&J>Q zO4tO&jC7YS;pdU(S7Jq8e9K?gWoga_RNgRHeW&E}YT4hLHF}5mm5OD?HU-WN>+k7e zF3#`l?^DiBdFUK`iO;z>7@oXA7_7-K^ZL4gDH!^V%Ny<`s)~GEVmGm(?j=L@9*N9* zr-{4>_1X3ZC)PoUKXR}g^?NI5i0N--g*92!uVlAfBiK(;>LG0}XhbGHA_qm@UYQtP z@8;{tUbi&u8WGja7JBJoRUKsybV?`iE$;-e*1lZ$(AN26H}gTj%ST6=7w3m87rX`b zE9XJDZ>HbxF|G~m9nQSj&tKwsFLiowOp;(Wts0o5r14xIaw#20?MWU)fdtnLt_oK> zr3IeueRMy!VUCYoEsM#8$B?n=N(t>miH{UF?iyruL0^cL$gF8Q!;lE6s~9norLY@J zjNPE;_r3Hd$t#c!%pF`-4y^gfZVOPc=>=Y1Xq$th`SIh0q3R%NWBD}OcRd@yg-pL2 z&h$#MX9DnR81aZG`y&O#`@iTat^NF3r;mhJ(BY94ss4VpZVx>vQV5jxU<8c#4z-p9nR7(H`O?mjfKgQD`gx4$aj1W%Ah zpv6aMocWC{@GIgp)Fm}B0*cQ=kX8*xmL~vdy1L-q$uzE`n(pYiKWvK-r?SzIQ!gQU zPfN3rUbHMk$3<1*ib%NF?R!%oU!*`+3!K>B#hDn`lX8Y&kFIe|1B^RSfaC*uh8iyz3^Dm7*x)V`zuT9^)5&0Q`CIa z`n9~cTxDt9M+2GzgAw2cc3|TCSWPr@@?1_M;SAlbV&9qz|AnnfpohoY*`WKprzwRS zL7k+Klx-UDakBS|3QHy?2t!`qNFgCB08bwJ>NU+n{5sktSsK+jTCbwT#52Wp)r5D$ zx1?h>YPa7hRlnMtZg%r@7yDetYH?5VTU*f>P zl3m;d>}_G>53jhR{&X->Vt=5(ju!E5TRN`#c^)f}& zQu8HX!W9t3#sj+fA52`-V5WEVLsjRYFS@5C_hQC-#ZoxytS!0XyFQWW&!Fy%DNJq0^>>9Hh4e?L|fa zFAYDduc#YY2NPLXskce5#Y%C3{m_IL0=kHbbHJlp6$MI@*JM0Bn>w$go(a!ooelDjt*s2hdFEPkpbz@G!5*^H=pE6q`;G}pU2lLQq(fiUp^|rGDOkA1TAHSS;`3b)udpN zd;Dv3;^ezU(3zNr0A&UVEPF~61LsLPd0ZANy#F;}%}_{fQgjK)hzribjP|K&$dhtU z58>CQOXf1* z%prm#swV69a=!CK19%92Zw_1kogcnGnRY}!2q~=XDMjKi4uPraF(fL1jCk1;nv|d3 zajT!jJn%F?Cl8^L173VANT1Y>;}YeYe25SaTGEoS(%vU3RM8P{(}sn7V_;mTkjbC~ zH+88JJ>&H~(Ajcr!M7zcQX-&W&*bvxN@KU6QMUiwFa0LHU6;=)POaO+ieF#D92td3 z$!3?+U#2)I%HPY}aPS2C9V!Yy!b$aUq|-(%X}=?HO1%wjbpuMY*|oABb;*WOnUHGO z9d`sY-sjyfGz_4*eh%MKI>)Z5S97T=4RdD-(&*$fQs-?pcv%LvI<-(%BKK4`6Ad2S zE<|F$pD4DF6Y&zaHzz82fbO8|LRThtz+fg?~&6xRD%7@t*bNuU7hD zb(dxwzWCU7ake8DNpD8HuQ<2_^mur0Su@p1_PjQ#Y*U<|)gwl>K(V~P8u#)QAn;Obk@f}%q`gdpi94i`omVm<4r z?%lfQ$WQhKY26g>3(<+`l5*0S)ObdEt?Hji|EU}`n}U3dp@r^u;bEC}mI8wI=|fS% zZarmi2O1;~VnK!p{r#4NGzWaH7gbG=wpy>tCltW-%~0bCmBld9CV^)SE#`Ym+_%U`<#s?3jpL~{<4HDn>^+cj!`}*?SeOJThDJK0rmg6@#TE9ZH~N6 zO1S&#I4j0lnn@{?5kGpZ)VSlL>T9V*B0!XZEw||UCld9|@3@)Mnd0hZWk}Ub2-fC4 zMXJqR6R3LMox42pB5gQfDW$L2xaQNEzbqr6#2~{hSWj8kZwM4{M|Wz4bf6 z@0NO0jlxI3kxC6Zutx6-ah)nbapAq0z&nGi1gPyFd#l6K@_CPy0mTI%R0xd^d4VQ! zR3vBv2jMW5DnxC_|5f+{6b5(N3?3T8PU(y{u6=u#92haAt7VtpVT+`Qnpf$RdC?f}%<_S!njy`eRmFJ7D@x@_(RE8oeaDpnSIpjvBFcPV4n)5$U$ya>KS}O54CVY-qm?W`HK)FnGb5=IitgdiJuS zq(%@>Eboa}m*&c^ba6Q_Z;q7Q@g_O%d!|RC*7p*RT`4mgL^gWCp3u23tSFty2;#F# zMwo7|v@jV$wDflRW#kN1wW!Fy3vwQ&kNaVdbd7w@Tq~NExUqU9QNNbUYJE)`RCMN- zM@{*z>P4#;kelIbj#SwAghm zP7!@J?Z#D|U1D_DkoP_LOl>@NcAWdVZ0wFVKy0A0+BtgI!N=4clInB2uzk4Xj(;tV zbsh=nS!)X)UQ01?BFWw&e|gp zN2=I*3Vd3qR7vv{Drh`<1b2j!7TqXzV*$mu(c_NHJP7_tAmzcNNm4U36i$ag;$0^t zVs{GEgB$BEI4sAmTQT{GSCV|c^$fG+GhwcCPus8)J;wa7pA{FM{6r^O6`(e**b!RM zPdx(qmOO(6Ea~>!JwmokX9Iv!mCJpvK!KQ%^Z|q_Ihy1FaTw*kf>(# zG@->45js7(Xf*+RU0Ro&1*5n(eHVK2IFHJdYenDmxmu~HFgCY;k?m(PA!)A+87pQ# z01-XiJDdwIpNY@L261_Ir3IWIri?Ww_KSMDoKQ)yCjV^LrdMSru@g@kej}eg+kHPC zr!gYLH2?FVrU%Km1}O#nD{2d@otcLLBq3{AxQ92P5@zk-v6(RHzJQ?$gQI zG+K4z0$azT1^&Fn^wj1fkkqs=UmX9TM+-%Rotp%72-~EI`4S;s} zIjNuH3*)8KL4YzQ@9KZev+r>tdGX=}*AE5GekokNp(4BEf9XD{uXuZYcdB?;?br`b za;6XOXz-slT{@U|rF60S zhW^(Am%ABl?yziQVwF-2K;FE$a7%A46$eO>$2s*2u7V5jxY9iMPWgu;qhuCgHbwZ%VU zB$X)@%#!{Tb-c9q#?Jlx-L3nUBSkP38t)d(*jR_FO%6nL}+>B>)>vv6KC}@1v!IoL=T=+9drm zl$v)y=cg>`Lgj<%tEeKnL_O1SRT%Xv27eR(%@MPi^ng0}Pcy~LV7^2NxaqWuPE+};6vW!=)-K+^(sTNKvl zGGHpe7qXSbMtOt~TX>34Q@`iOs?qw>fwiPIJLwgrwmm+9l=527#6QZ!%%4}7tXPA- z!UVF5w5WPTgtjl^MzqO#{We~zyYSCs1BP>f>LtMWRrK=GJQMa!#=qM!!^P;h`Jt#Mf;yYysa8$pj9*;C6tol2 z*M%MC0$r6T+O>Qg%HpBr5XVMuAEhv5fsgu%6E7ZDcrUS8E9bK%;J~EbGIhHBd$sxa z(|&`1UZ;CHa7MK+*&yK9BdXi;A#^iKei6{iNu*7$8qy;@_nNPb&m%eQ^5Utdb8?~u z9Y%ZCTSsC9%g0$s)d2^d)aJ)Fk;}(Y_f#A+zZtEz*4@BJ#S?j{asZpNt7W z3&JUFCG)zXY*~3ZK*JoGoK!omlG61lrA85z{MDG;DO2W$)%~0ZV#5 zW-TbMqd_+9n{*~^6(#V{&oo`iC|yz9iJ*_Y$pYY{W@Ri@uGi-D0icjXi8j540gV_# zUm#ILj{yqP4Zy?UN^5J$EezP)>Y1FNE1#j!bZa6L{|vFfat) zy=zcYTH2()e@xvf2}2!ZO$pgo`w~2cae z-=u|NQ%zx@byCnckch{b7Z@}@H-oU%YKdGBlbOYe$;;JFh5M_3%&^)YXwyY!06I9n zAA;Bo_M+n7K3e00H+V~oYdp`}W>&Gq5Lm@3LjQ`o#0efFVC^_s&KPraTA%@xjUURg zTPRl^M&}LyZriI{|E@VJGdckSWQFegFAqJ>BQ-uxnwUi>pb}in5Zz~i_T}I2R>+v- zP5)QICLX#EGX@lOFhG8$zGQ#p5A*y^ssBk?Axii+0ZJ|ng~~;O0pCd!(AM@Wz1W=3 zCy#g{r%)^&azP?GNDF-V^8#jQ~;rnpf2(>`m{p6%l9Y8q!pJc?HbGOx!- zFIXN=Wn2&tLyqN87ZU&%eeCtOkcIPKSK~eY9A_W3Z93i{B<6^IlH#C;Jjt^cD5} zuCc-C!A6Crx!fV^FyLT71Ij_>(L$GGZbGf9C-G6%%mN1b!)#3Wl$W(`N99)SCx?Ju z9B527j$c`sd>7;O*8!>N-th@lwTM<{_(aN+-|V+Z?>!A-H&A+b{jU`8n{^9|T0}#| zSqI=;O6*wxTqP_w1pDEi9Beq41)f)(8)?W5U)Ekw2$?8G*3DI(l*^8oI@QjFRRfLL zV?g=$(L;ITNZbZ9iH^$~ve|SmtvI~TXPf(8>88Y*X!J}g+#>+tn>|2_B?O1B+3Wo^ zQE~B*_~~qzfBzSN?(BSY4`8M+@!GHR+5+0Y@kc3$go^Z_^k0>{hpJqo0#%i#n?mmX zwYAWm(t_H%<8kc5-m7`*es0;6KzbQT%+n8UzJo0;q${lV+H{hE(7#at3Tg5EA)SD} z0C%))A0ym`=Vfci1p-}>L#tqD)!Mtdo8ETeWC zbRmFpM51sEuub}v8CtY%1LmzRQ@?Gw8O^WyWu}7OJJy#{-)=vaAFG~pTAK_guILpF zplyXP4V6Mre<{sJ^X&Xq-Yi3lFKn1RZvU=%8~FD^PGKgc5$GkEWT>POsZS{HK3KnI z%iXfiF%M<5UXHudfWyS_i>4y%9vTT6vC6BA??>ZMaIoXTYptbmEK&?LIUfca6QpHTx78T>SA--2z;)~@O z4Q;GjcGzy)_4vNikW%7TWZU!a*@42pUpDisUB00|!?(1~&N|7rH=({&X!50%ds+=+ zwb}+yuO;m{M(dmM_%7Y&6DLX2y|NC+!=H^M6WzJT&w3cA!{NwhRW4FmV?hp}GBV=e z+bW(_KrdW8h3#%iO4Wx6&;1Kswbj?8-Fsd@H<-rhMKuO#TUz5|?B`lhx5PUNYZwRg z3iItO={}|Qe7=K7=IGi@VqC?kXog98M04Y*eEpH)-Ns~pOL}9L(@CR=GQIm|+tp4E zK(S_dYDL!>Y?(#Cpji%T74xWS85st7-C>WiujSxeSpvm;ate68fHiE)m(=#dXP?I~ zHU>C)M-mh=GIH4m>{I`y#=<`v9^|05%0l?ZZOcM>%xV4wR3KoK*=HF(6UK99J~}d> zYz_SVY>jTjbiCG)_82x|tAdXLu)`Tp7I3IhtT0KncoJeBsp zgC>>f2Vx?Tvh7J;tglKjXl#fy@0nn^is8S71OWwe|2ocs^XT>tu$I_e8D}|B>tC=I z+^ZVC_W>0+C(w=a98h8bx|+|-IZA7@&jGt?C=7zHA#mkc2F#8dfZ?Ecw#0hyzf+a0 z@JX=k4Tg$736w8ucWCub_M$Q8rgPgXE&K!>ol7%r_1=KWDe->8;q;{VJ-gZ-L1P=Q z#(h9r9_4k-9o&5 z08C=H5hHV}e5Ay962L|lfZf0rv+|ZkPbzDJmd{po8fN@*E))jL_ZrWQiN&!jI>jFl4!U3}ipyL|c05pkJfPTIY+j#0^?9zgN&PXc0 z7t;X9J_WBVFzcUN9S_l7ha0lDB+pz@0xym?!f2fS(y9a2kq5?rnb^<%-A*3>B+W3{ z?2yB0-!d4mT>a&(1R%m}!JyxjY1FK}2x!pti65pSDs8`o5C#|`x$;-Ra*jEXf;*E} zit+I)Z;6KZ{>Lk=JF{A{HDLL(tmXI{F#KD7^5MU|8}vf#YPm4;$wVqmI-yFqZ)+hG z0J}R-mEIQ|HE}-{d}PtO5l$a42H1UwQGkng1Hhh#xZeQ_&~G!L>6cdYpPv8@w|q%_ z3S@m=HM27HGyr1MV4BxS=^W!&G^zp9^%1+d0=<9b75r(0{p`<@*#Nw)H6I=;oMisK z^3q?Kq~)XOHfojuWA|1b?Ug_`@*dAOb&*AiwZVeuD_8|I?R`(++obW*>me*10D#!?c`p`3T=D08SHnz!m6Itsj$0ZR+@yfk4>j6Pf$#u2p zg9#3=pz@0oxbG&BFa(h^p5~Rz5nSA5NAPdNS!FW|aU=8!W8b~ws_%#r#ju)De{g>6 z5!?jKib>$vOus3`Us7KKxw5l6UOCYaRGi?J0XNEzQve!i)aR_a-I-YVo>I@C1&x5& z(~5e025d%rumSI-r1r$2TpQc34{-@sS|;6 z37sfaXat68@3ZAKU7U0^VFANfo)3J|BakqJ zxT5U|U?+|>ghrGuN3rg&{A%q5Y@vfM@#!#&7yv<6T^X6T*_jAyy6kELlhwt2Li;#6 zM`53T6fuT}NIk&UMP@<4r8A+-OkU2-qMrPyBTIKMOG+hytu#(B0iJHM5_=R*!gtpp z?Y;7a9K&aQwcLmm+z2U;?J~Mtil-q;o8UBML=y^z#OSrt`4(@cK{Nahwx5Sn#~1a; zf6^t&iX|0L)Q8SAHaNFtt@6fAM6oiw1bh-DItA*Dc=CfU0`LW(R09=u8UTrcQr(ao zqW$c%qW$<~6?hzQ2b^UrX?4D%9&$(-p477)*i|12GKJ~tGC9Vn3A%c{MsJvtgLrNm zz`+&neJ~$DT9n_I+~AwCX8mo-i@SRf(3bst`1FOQ$cRze+D97`6-n*r2;^vjm_5yP z(zlmA6Uv{T9F{*>Ix+SG{_Q|8U})LZafT>S0EXIQzug}F{sE7yvgs1Hc>Y2UY4ma! z+it^7qM_}59d13J{w!)#QJRA7z(HY=8Mx=?@Y3Vay-=sXrLSMh=y3PtGUL>X1I8ey z98{qx-!AEQAG}R*(soI=v%d3Xc)9h1LjC@t;hc2}I_%a-m$9J5cK56Q&{>)fU;ied z@-?tNALn2C4S_v#!>4ysCy{V#&=9{%%vq_~J1aBz!k6L&CAr`KWA87cs_ee-Q5ZIx zmM*2G1tg>!l&NyT6 zfdPBp_qx|T*IaX6^P1NR9Lr}(GlmvPh?0$wIfy%)b_um~Yq&A*KGGrny-TX+i<-ep z7uL1hTv%U%#w=UV>fMqdRrs~v0|54Z{LxMst*5Zm%BEMCgW3pxV4!*7V0kz(9+ADJ-3wb-smm*NH{IiGSfwmBtob{0mBRd=BRg zOD^|zSfd|tM6te{dFo&Ws-A^&FXyCQub5V8rQMVZw$_Uo8;)vtgsqIgnBRM!@(QF9 zOVsM>tnw0tQ?^aWgWf{dQO2qHOG>>(XanZ`FSAYs%^}1|x91ie7?Xz&MK)x6f0Lj6 zQvrxl*xNCrI9cR3E84YMHK91j{x!q z{Be{h0lIv$5x1E%5V-mJ)CCI*G)H`L^%=NZ4G2WDZi@|@U@zVofl+D66>H zXqqep2iOHB{vh4-Tz=g^rU#Ol?XqG|!ye^wgU$BfnZ!A7xBE_yF#g1i4Io4;^@U8` zrsAD#F;#<1fkdFzZZb+l7$@HOyK+*Jb%6wWb>r@%#?{z{N{`1E7(B@sB$`Rx*ni@~ zXr=6F_@1S9^9F|Ha$cl!O#E{ce6^*Y-FRZ1bq+3a^R7>;mpcZJQkhDtWKFK_2aiWw zA8S^n8ti3&aOp?`m3;t;X69pgDH&c>(_JL=UrTVY zHHK9~LxA$khmA~gw>jh5Hi&WLbdq7}@wSEQN`#C1oqufz0~P-v+&#{oH*h)kcdu3& zT{x&wdiOe#pF@vQ)F|JoVtD&@FlraQt2yX1r%yM^J0UOBD9cmgY1|X8_D2{TFD8Wb z5d5;G=Wj$uA<$oLf63|Oz4^UP;UI%JlyH`j zkv-jPl;Ey48(bfbhK1Fo61FpPJ5KG^ z=7gv6Yd;fLqz-8N_FSj4;fsHk!%l1%^ik`60Z*?A0sh-FEsxd z!fXs_TMFNkzZ4JsV?psbK0YnsUkd`s+$^|KjrY~G=U=lR%LsBPRXfeUqzpuD!2^$< z{IuBnYg!o!!L-WM>)iWyQsE#qUy53l(!^K@vbz2@Wa4*S1mAo>I5nsCU7Mr%&tO``rX8Q^HD5wH_H zpkk()y&wLv2TJ8)$Orhs3F={=zt)~I5?FhWva4nPm=Z85D^PgL(Tozj{D&X#YekFR z1BHfxq~#wxw)MuqRm^_f#+bHJo@caFaXV zOK|@+VmN8htH5+LrvV_3GdR^irQ)UjdZDwtg+K8WvYuou-jJf%88@wQmVqWH$}Rkp|Br zGhuYspO{eF>*r^fqJkV=GC&W0faJd_!)<}@VHQllO_UFj*DygX%Y}Nx%YQnx6(tck zC)Yfj4Vn0mw+Onu%gJ3XY5;0&ZpJ9%k#Rj^Fez~}s# zzt>$-%OTqN6<6?KdhhH)#thZ)@Kw9gR>pU7K4e&q!fwid2Hr#17*0>B5Qqs zSe#s+E?%yk>`aeo>O4y*c@_(kEaj90$}XlKk_r7^q9`LWk#~odf(#CVCAjYg18H23 zMx|zftk*Y$P(3DX(Aa9TztnyL^5XMRp!@L=U^$=48nzg>CyJArL4y6Y+)zYh7gU-) z*v&UggL$w5l6Oi-%=L+KQLzt zAO#N)WwzMC%qkE7WgvCF^(Wb|V2Satu0n%=${sXv>tqRg$3AreLJl*aG5k5TROFF6 zKq*?AfhL0Memk<3vw4NnE=l)vm+o3*KqfT_6w*7Qd+#n{7Z04eQ&#(lX+>&dbV z3eEk>9;)yuPy|gNB}#ll!*Ro-ys2qp_)}y!B@2E*in{HbaX#TNK}XN$eX7%fg2+}= zc(`bIl*kS5_wmBG)}Ae(LZMfi_Cp-TX)zzDupG;yovZMrJkp|EfL*q{s=$7`v;Xbfmxtjx8+;Mc@A(ioBVXg zu=27AnAC@HFqzk7jD1QRox(vb8Gn>KP;?*!sv7q3g;72(D`}~W^I<=pc81wTdtBX% zRP;L}*kGDRVLUng_$7&Go+pf!=xH_({19HSuMAFsV1Eo~n_t5K%~;ALkc*baaDvuq zn2<`vyzA__@#I>W2t^$?g$xRjGZdgg5x%}at{55Eq~ZPm$1oJg9AX9CxrZxw@%_Ee z9FUtMci#L$dj^!^AX?Xqpyomnz;4<_lhgt8NhA0`=14)slpM;aB)a(QGijL6^FV{0 z{m8ZZ*a+|mUqMZ?LvMe%II{3aEtjguAa6AQ`4SkoH^+Mrjl@%8l9IAW=4EeLN z**%naL&_R7!ibT6U%@E|RF(z%#AG6V=?O`3P(-~dC@**uHPv_hs#bAIfaRw)o{a3@ zM+n06XA~5*n1A`qB3$4zi#mFT{=-@+m0?xY1aGe?{=d9>5s2?{iZJN~0Fn2P9~ONE zf)4eQpT^t20|{72Q2>)LF5*w$x%2Pm;DGuch&rabgG>KSG=R=h5%7&gTlQ5Pf363v ztPTr1{{KD5cuu$!F6%^vbl%D;3F1l@Ul9LO=b5WQx1dKtBd2fKHy3?xxrCjjO#m)a zyjk9I1T>1wB5q(ZZU;TjGvTsE*Ut%@b{)N2(j4uW)_t@NtNI+|(d+H{U3^N{SK3+# zq+mG+xQZ=U{h7a{k9Mm1XF%yRrS=&DGUf*fBK>hi{!JRsC4XcaQ2*t;mj2Aw4D#z* z;M8Ve5YSWGngk)BbmFi;R@)p3$@0CaCvzHLzo~BhC8E^#KO#zbcr`5?z;UDt0YH#% zqc_PN=w8lzCNfNa;aqZgN4oo)cc?jTXW!MSp|BlyxXQt&`p z0DRR+@fL__&PtNln!&_9W?nw@1t79Qe{miUKh}$u^Yw)-0OR%7TOW+MKh2Wxt__PR zI_J>6lcmaHI;ro*<$cp}1O#BFfRaao;Cf-}Q<(1L6HLC&L<0}YMu0(=j&Ic3leiDE zMsKGdgEcxuY+bS(wfV0C5bSh3$#-edfS+lEvMEy9gS_SlfZX zQ<~G*FFldCmrE^k?cYb=zXD1T_n!lu#_VgFiA>mrO!UEK`qA^zcM|(Rht&-J4!Iz& zHbBf@X_o3D0il~EI!n(?Fj%|~?TMs)u7KxEBi-ArLjr%XJ@8&{13E)QJHX`&KV6*Ooo_@Q%TquH zEP#+U{pW~ai+!fy<9l2`YB7e`mO^uL?T<`;cK}#KJMb!*PF|*utT#Dk?j||Pd$z$N zLn(lE=?H+-ZuurHZfhnD4%6N(E-P?P(dAfDZRZoS4-9!Cb z!N)wwFR)z!Oq~KwgDjuw7^s?T@0EaEcm{+Em`_2Jp(dp@SFx+TtvCX}B1gM4ftXof z8a6CTRT`Q)pIMsrAO8ZTfL-^m3_~BGg(1?4lZuRc`AQ#lw2j^bbQIziO0EIJf73I%ulg>Slqgvr+e{+dy;YMd>*T59$?z*ETrCr-@x6>St?G8CoC2a z_ipCK-b-OQO`-^c-8Abomx7TCRqer$?Q+A*LDrEGKDm`k011@0Toko(qb9A|sZN8S z;JbBppt}MeJ*V@0qK6O0WCs)EH3|sDRoEAGtxKN)Jr#`^4}$1A0j3+1Zve*Q$ym}u zed3r3#I1T@O2ED<*qSmbWUBgs1=1=+PZ=3CT~IZu?P;-F(87zH=bgtCK=&tWnqp2v zv@%yXOf9UOwMMb7B^g%B*z7E^1Ryz6>kSBdx`E=N_?T;bqeLxG>6!u-WDN^=uyQ!l zQ=ET(ll6$5E;4u@o(2j~w1;;K$l=}SM-7j*s(Fn+K|x7kb_dLvqEFC={P2gh$ymC_ z@Ar0z>D+EWL~|msw9Df3NC}}M2;(q|M~na4;qke%+ls;jfyQU6A2pNQ8k1;az`t1* zQN0~bxtW{yp@o!H4l+ZtWTcN>6|&3aTLo~4$7`!n68ytD`WJo-RV&a&|((62djGStoUq$WW$yGVBf0k2+R zW>(JIJ+T@uNe-cJX9kNIFm+|hClf+Inb5i{P`I9{41W}vu1tKwuwrX!^P{3hA%_}J zfYQxpD&^9WYx6}u)*0ve1VqSU>WLS4C}D;%q3kweNk3Mv)=F@pp!59gYnYyLudbu% z5NZQzy!-suk#jEV*JhLe*wWUhP@nn6lI=Ab)|fpqD>{*&J})LSp+xS!iX*b1A95y0 z;9Qzzpn{?EGkwR_wT;Wr5V^3-j?P>hsbv-6QiM`xA@mYn}Qfh`YPq zHpNCTR?E#*o>OTficE39vR&WVa74-c`J!)`**@4-kjvB+U$tBmG*bQq^J-TCEy{bl zI)Pgg$&aq}ioV@ozAmfF?#M#dD1B7T-}H54%IR_0cJ0Mb z`T1!8I|>c-=(2c`n!*#G15V{yRbQ__KQPv(O>ZKnQ+M9ijs8nH?;TM+uJ|@C#$$Tv zjBI|FE&Muxmp&d7o30X`{?_Vii;X?P_84^6uU|Eq;(hl{=6Bt>%TR$bhDx8lzBh)fl2E>+kC3Q|^R zzBvi`;XGQl?YARahugBUS2-#0A&*>CNRcQ#${QLkOoDnf9-yht@-ejf-rn@K@u#yy zmX`&NPU?I;<>Yt`A2QSMohCO8Uv}57sWmj_p5OCyCU|KiN;G@DUdeqBC|JfT6`_|K zQ3Jj79iS3!rO6yj(6m)u1(T&tV=olIF|c_~JpUZkR(~@XOP;fIraTvX+egDfZ$o>2 zRR<^zsuJB>y(ACB)9AP0oXg^E1%ABtMoM^5xNZi~zc`klU{JbD{f$#vqtjk%xAA4& z{$;T>qUs71jn86}E@CL{yv#7wQBk_tj_K%XrcTIoOBYufLo`hHr_Qn5yu|Cm-M3-8 z8WZ9g>5jC>c4Z$r^kiP`3CWEW1gEhTDt;`oF4Q8>N;Tds%P3?F4|_46f!{y59lfsQ z!ZzWpUW0Lr_V(2H%cHs1!Fy{@+{+v)%ENFXu?*FUZ35j>%6Es}mCsDPeG)vN(p3Hk zUSrL{->!MSxkx`q;OC_k#_l`T;*H#{=mLY%8S30TFCn2^MZ92R4Fcr}361u?B!B^| zw>~Z6&oM}yi-5@4K8brFoZ&XEQR%UY9@nyxvS^|4P{lK~j(%cO7|6!SXAOJla{{re zQDrU?jc#|43MvTAuF4iEm05jYVXJ(Ws6$t}6`A+qyQlKQtWiB~hyMPOndJKGqSSm) zpp+C{FDNQwdSWE^4e!SMt%vSLkR@2vHldhUD$i}+T@l<*8Mh>mF1n*D-|MuKg z^b~p|ABW$+{|aSnbt!5I%S4)nI{UU4buxpZ1~yklc_L7Jzi-;zZ>P4}RZFdI}?sjMe=JCIS!>V$2>Cnyyk zG^yo>V{^$)%=6CUj7q#>k^Pi|1Y~zR2xc!9s*m0mG`cNI`0{2L=sP$ZlQQ7ajoX}4yhWA(NU;cJeS)_x?Gj`I5AhUY)gawsuC-y z!`cOcOy9Y5s((fxOu8E06_LaQ5&d0=wrHiB9wL`# zOQ(R@BN@70GxOM#J;C8CP?_6KyNdI49Ofe;Rk$}tS-FQyu6Ngmm%<7?ssw}HUbNdK zG{Ho0puH}lu6%GU@#zp|9$d>7mZ=3ig*`j-@|&Fn63A%vT&u6~Epnbh%SJHV%?s=Y zOy7MwyswSaWqfAmCb72IZu8fD@hszyP?F(9RE8|$7okT3=^T2jgD01%3n|75EzmDV z@S_w2Pn{7zsp6p>PWS|0Q`vm0!2^L4>HTH6u;f)~H4bzEeR>m#HO@~FKPc^Rm-oY~ z>{Q0?$|Vq>E3VozOy-XxTF8&*_jf0zdTUn*NwqF9V~u{tti{*?xtIECV7@uh$pVJ` z!qM&?e7+M#meuLKYi`z$*DZ}NuF_vXraTdbMy9MG5DMCKr-(W8b{SG1ija29Zv9T9 z?*1J<|119cAFkgiBXJ*}iTJJ52p9jAo9|HtAmLzJ;WZEL#Lz+2eBEs1`8o zy&KK5F~nf=FF3Hfy=}imbXdoE3Aue*^irF+_w&#gkYEj=5`B&8@s7Vgpf_v+rNW)G zw!gSLBoa{DMu9OIjibkua8ujvizl4<9AWS5;aG6&iQ8GQ>K9tS^9bE%Q(nurrao8- zOfo)rSL?Qe`6OJ)08I~PeOEEkfxuD+gc)XMg5 zJeAw`k@A^hq#j@yKsKVs6u~Ob7C})$9%G(rPK@KdX4N8!ii1NP&xCvIy5feYwEOOA zc(NsU+6G7Ib3`kK=I-<2tii)hgBVdE)cw<%%%j6u?kkkHFA8?==W^V{cM)iui#k>C zVd8!~@E2Uz+jR`_B%}GRM4EQRdyQokw@D)kIi`kNLwLMKy}-xThb*gd9?~6IFO#i< z0xqXB+^}U>omw&_5L3)2*`7EpLa!#{2bKQoXGpVx~<0pO=uJJf+hNzAMLwy%Lvmp}aED7FowA=Bja zEqs|mGmW#R1R_nTb>VgX6PK+48BJnAjzizfE+eY(%9Ti}%RSaF&3(6#Q<eMv;*di^+rj2^N_zwy_HW)%V_M$w^Ir@R;DS z8SLKdiF`3Nnt*B3fgu4@LD(A^g>=*K87Qqw?pO6OQfV1HIJiEou}mr*&jPo&$U->z zV^yluYzo$&!f~V8@_$xs@VjjcQRQbcR(&QsrjRVwr1HKDGtyWMKo@uRwUoufHo`BV z(Kspd9zSBFJVPtzQ(>_*X=aeCq*u^STzjFb__QSGC@mzL-P>VQDGbh!bQ@2#h>RMp|o5ay30{lwv$>dAKK z_dxQ2AgTXWJ>^HJ(YHt~Rrg~JoThI*11VJHO;iUL+YYjb8}n|4LF*WKaf{UIq6eP4 zQ%?V_QP8@0I@f!xTHa~^RjHn1%}Ky3Q7>sM>yac0{qTbIOXYj%+nY7n*A!AcOJ({U zhx^ZOP|W(EOulDRrvF`~huohkIf)b)FvItTHbrMyNe`V+d{5_>fF(1$oYc=tG~3e-m3fqDR$8h45Jefj5b;^9 z7zFs!e}1^A{7_V9Ur@AGQutuI@76Cl@&*YSoPR@)LB(q-tZo(A_v?AlI(U=0I`;J` z{e#bsbZCJ$E4B!_{>vtzQ0_&&Rf=0_Wws6pw!CMFz4pk{HQLYuzs^se2mZYmO|&h< z9->5r&dMAR>Jz1<}OV3y(rZR@Ab|e|uDtSUwW%%B2 zI>uRlz0CXY;*qjc;jAoGi#}B7%*wjNmark~c)M&Q^N=l8T8&ocFUFi%%5)R7(k2#g z!%MaoaT|YC`umwHR%5BQOX8Z?#p_eK*|=p*fuH?ycO&NgUps=1$L0~GxQ5`|Lz6WG zW2fT#`v{h!k2|Z1R6qkdE&PSwcr5qqU$f4Lt8G)ssA;fK^t9&SRIJeHe;$625QJF zDt{z;IH`TfL`e!m>MViNsRF+`%Y6>i@f6WCZmL`eOxBz33ANIkBnZI@ysX^`hzcWR6tbXpc9YE(3cGMA={?Pp2Vm8 zlNjGtpWF|pQZ{Q}e@!HAKlp~2792vRBKrsnHCj{?b?LSt=JgFlgoA6LA7_{QDubgU zT$;d-md2hTf$)+h$ExZuvCn`8^N?OGT4ve zj?pSte)1nFTYM7os_XGS+p1(w`1&AV@@KceWM2pl z6_(&qNzv!>7+PT8TMBT+D-n#!!aiqjRiq~MR^CCr=xwCEAG`}6$k5pZn5w*ak1SoV z%%8q|3-Wx0;;%DbUnOMwUdw^5&zSRdxpLe0Z~O*@PDAG0^Gze;4bW=}E!+H#Ot#a= z-F6rPfZ{rz4Im)@)C?ccJ~t48Og$iJwVkSl$C9Y13fJJXF+$EIQu=N&fSn?&zdEKw z_RQ^@c+AIzzKvUxV`;kL`cci7^Z3H&;;Z>ta}Tc4wP0(&V|{8*56`ds({ivt8h>ex z=5bI!+6{ZRdijmj_#5olW>0}Mt@hUUx&aA!)kBW*Np>wd(6}Fhz2u(i-BdDvF2gA>DUCrWD_%kCx1Nb2+++a(5vR}qS# zOq=YsEOVlv6{_c|^VPHeX5tl`YeUcjqa+q8>oOf3tcu409{U%&0yxRCpWfip`TO4J z=*$nQ14gmL{^R{vNjxaQTTUc+uopRy^M$2jx0!uZqUbgkq1P|d;rS^}VY3vaY%K;Y z9aF>ul-L-bYw?$HY2HZK8rxswEULljGt=E?h3=?r@i^vh*d{x;Vr!Wj3QRN6@FqYk zhJ@)HAM;_>vh%{|-OtU+jyf3WP9ce3zwwdru{ zy;t3(QycscUs0Y;dSkPocTn5~-$n>?y;-KmU`w6?Tsu}IzbtTX;);Tm{C&Y6hm{?T zsN6pjC_Z=GbMRrM1&55~T1G?%6nKL>wGl*j@kWtKbfLC=`2L0?ovrZRp4DJua01qu ziP3NzGs?x`CeiJncZi(u-nQ<1Bf{r9 z=2|)`!gsL67eMVVMsiVQ>J87(B?uybq+~koz$^+Bd+VOC>anu$+!YXW*Fjz8`ktqH zanJLz?X5_ed>Y-s;%(PM0wED{Jw0!iqpRI?C6R}ZtP=%ZXC2D>p-E>BlV4_{LqZlT99){`u$s4QJ# zmducJVy>^}tIr>a!of}GJ`+n3D|9jjedMxC_Bw$gx z3z+V_U_mhoHspQOjU!n4Nuh1$am27RI<7eNCzgqQ*7mZFsn=(K*5GJCPc~HIFm0q| zRw8a>T3TzH5n^I2@a^WR+LXFwRFcYeh8L}K{iR<`!suE_A)zBe)JPlOOCU) z5-j!NG&bd!I}QOMHdCb@KWZ|X3CZ1Q>TW8iL*(&K^@1k4O{XdStjTVae#~0D7g_W0 zH^SLxYU^XnO@<@=i1Ue8e!uZz6sUbwgqdmN1Cj_;7eB zr~=e95gV_-DFgNIZb??=?2kOXhXBhgwmC>aedmOb5+S7p!2&`Vu+hU;gNP-n08-6 zq<0n{DXOJ}Lp^bMK>xC*Ya+@q{=hw*iSk_V#g0o5Dv&no!4&A+rlhL{v246#>S7KN zo`N930oqA<+aOYy93i68+)7nD&1|VxvP1g^m@?AG>AfhSb++V;Z(ach{tZ6Y6P`3P zr(|w#63aE~_r>>z1SH-ZEZY%HB9Tfr2Uz!lZ>~(~Pwnw@bayPgt?LxZH$MKU?TzZZ z!vLAR670+LT<`c;^3}#c1n3sWIC`G8Js`cYn>ZQnkoCr2L%`xXs3T|{8Y(pf^GhHkzhmHkd6wQ-CRo5KlOd_pz} zIP#I7NqpgvN((jdK&rK5uTGjE8^@vAqmheA!*C;wK_;=uI|mxL-*EE`eV9}hPR@lc zp%st6fBcquohcv`X!u3fdM)>3&Gv>L{E#qyVRs;x>;kX55hi*rRah&gPj`8J zm(D~4@IdRqqWxFS2|~2~Uf6!N-g>CB`$K!$^}U&fDRtgOk~g{)s3e}@guOgOdS*D? zpz}sAcKjZ<;`oN!9P8i*GS`7sRF7`VY2J^@`wZu_a2y@YC$e8}aj3<+-r$_ClXTN# zxJXPJ;kW2av0>%C@4>=TkXMgGBgZwmW8CNZrQy&3)4XxY_dys0j>F(Ap`8YONcwcORTw%tbhr@fzq?<12g*|4~cDJUK>>nR!gQ}|pete&lqWiiWh z8&g4B{hCiP4W`3oiF{-ny!PLHO*{2Uo;uy$c+s-W!@WXOHEr%A^*(>OiE0EfKFcv+ z8zSs8sjtOCAGMJIkb$Nwu@ezBXrlhtP-gs(YWA5&vlZBuKJ0Vs=aT%H%JqmiAFwgZ zduitjr_b)(V(o`A`H&ErTy>Lev~~#6eX3h7{h;a~S79rqpMcU8P1{aR%7)>DjxVG~ zqMfjhmMKf&Q^zymz+2`l5>-<9fQ(k{6AGmXDP6h)qL1{{#LQ(psSmQ2;lqFeYo7pA zx+}OQVI6G@|II-y!}El&M9@ap+l5CH6oe zr5Wh6&}pkMuw$W$(^PyyqY7ssX0N87l_26%39&~ATH5p(VMLBh+izs^PUareIc}p7 zkW<=jfoo|hC%P;R|0DT-vvC9d#IsPUU?wi%L=cD zAjgc;dV6-=u4XMFrY?L|;Z6}x`9j-_dOY;GCflqGqw;{Uqd3@HLPF2T{L1ylk+JsC z{Y`>}wBAXlaYyzNT;06uky5w*IQ&NofP(LSXZ-=rYfaPDxBnz$Jt+RN@>$ragmJ5s zan!B%O}->0g&NY@EFI$VJi>as@hu^5fI}&@CRdG!4)Nt@9C_}avTF7|%k+oWr&Thd6N#EAy%176|))CMx$KakqW@g?`7K%&q#!^X+SK?10o* z5r{To$Cf2-yR2z4^qS7(aGb5sd1W~9;w6_2EnCSCbH1R8A5(SsbZIK|w?01iQFi@_ zXd+v?qeS>)>MPLjYP|CaZfv0-geP8RcD$2v-!J3sgBxg1SC$-g?g8n>q0J{ZaF%N9 zv5?KFL&<=uq3c;W`W&0ap`h9S@Ro_#y> z6kYZ!WcE78=Fj}%1wFb4{~9Vy`4AGKEAf-wk3KfOi_R!Bfcu1@TA<64EbIbq-W7-4U56jn@KB>VAI3JMqT!?j-&iWT^A%?NgrC_caPnCGpWo2|MFJCxeP`g5nju0Wri~WVr#p8mH_60tmiB4`C76}K8gm2$4 z^s(O(UcXyjwH%qsNcpBjxwrAvS6!a=7r<5k%UUL~`}osR+vHfej`tCeqfm_9E#gjZ zkTcWKum(~ADDd^X8t4Wl`Fi9)V=>kcsd_C?MC+}4UR#Lx)7bz33X%~L}#32dyZ5W=Dh?U`6GoUa$KBw4{ zpNYG?r7UPZpa27724NvNN0xVFGb5Bu#`|07cMTB7259e1UGqE?@ECt}q0G899OfrS zs+Qq2gaeMViV-@&mSO5-r|UNw%MI^w8kV4c)#7`ib96N^e*t&3q<@6G1MK~{*a@y zm1XxW_oHxD@$93#2QXaKWi8eElQkJPb8|xDe&WmcU-p-+f-*45fUwLfO(_R$xm1KI z?RTsmPg;mLVD?Ww@2>b}KP3((-x z>lcH#zCuPOd&d78qdWuo#<$_ug*3|S{*Ak6S#>5ZP&yXt5)}uvw*=~qHzQpLm<#R} zb+?F`A{DAt`=8(ma^jngzYg(%6AhNoMkh;B3=vPco2l7mI~aT3g(FR(a)cd;;9_j_eJFezQ9eNZm6M0AAU^Q3&Qiw z{H3bAJrY~|^!!+ADbo7~t8rBQEF^mU-$#e|?hL84Qrll)ZhO)SH7-GL7z`fjLMH<> z-p<;g3lCBMhBC3m!CkTM<;97jL6>;Ff7E>TAz!fi8Bd{fsl@l$GpA~=pq#K6MaDCf z;GN?QqIPq;+S!MU@1rc~A3LBo_j%ncCFsztnR+X2YaQa{O1|aLuImaOkM9#3Qz3I3 zuj?#x?LV(U9S9*oZ<--9xrW_!iAof)6Y&*=3=l76n{Hl>J>W8Y`L>75El|XT#V}lr z{5ZR%q(4E6-xPD3KJ(LxrC?^C7Na(MTVH#ORM2Fg0v>BKr{`tCOV5a(Mg+*kdz4>9myILsN&xBU(Am2Wj()5w`S3;d04O98M8eJ@ zFIwHB2Q>Jmxm+crg|TO|Pq2i>hZ>0J&?(NA#;x25nN$Fw==&=qaw5YBx_?WoXF2eH<5=y?`^~EY7 zjmJx8m9ILa(jI*9F%_SiaAi+rx8d^SsT2+=8)XSMc`?1eH`mp|Q)0Dxz?1rtr$90< zWkE5&x6XIoEpl`J6%s});GycDR-X^}@LUIeGq$X2y4`v`4v3&IqDu~7(l>lM&aiXk z6JKa&Nhhy2eA1yX_Hqy~lbh24>#u5DRnFaUp9yBmP*`yfR#u?of_OwFIt>%l?=~E0 z&>f?*3*#|l(YG()S!9ttOpU!~s@EFLa6@hslz?U9Q*c-~hpOokd-huV=PBHqVG*K? zc*({fC**Q}ey`daj(KosE3w3`Ry`1=Fau%-F@OX*N4~7*G)Rk}?wJnwcWc?NFsl+U zNnh%j%0EGEFA9x$Jwg8B^2f)IhX&w~ce?uh$b?v!G*35pc?*yEMPM}&QGDP?-YJ`q z<4XI(d7$6n$mW-leU!M)6k>9lhD5@juE~BWIGoTN>dLJ zo_#-E>_4Rr=lgc{I@!7;y%o7D^w8;`ckSA|swZdq;wXWI$p4F=GP6mq&5DT;_R$8&@DLv(*%af2(Nz6ip`cnaU99?SgDwqZ*7cVPOD zPeldZO9^byD4A=CdHwzi2|!@eiK-7hlpE!Tk^BL1Xv=E5zDWF*g+9YM7OyYrN&6%S#8bAUJ~Yk; zQ#)_s$d14cZBKI*0_Pppe&R52BmOKq7{*lgyl7Pbyrej=<9{)uPwyB+@+WzL-~3a+ zSiWD#7V7{#bZalBCS#;y2o4+!wh5Y39<@dn+>lS1Vas+e2U{#I3%*HPtfc()M+$Em zes%&ENmtVO>lq{66BNBNqNA#!*FHd{6S8rj=Irhav;iJ$GH8kzA~UhUL~rzGY;Rm5`L)H^AM1k+!+)J_q^dr6cr;G*8ubn-193(ds&5e%GNRJn zgnXm;n?d1$IzruoRr~v52xHGf6k=kiB_!Wqd!s!F;69+cORpKmi4wK*Ar57#K&eF0 zMpnv1^HsKXV5Jxv1+UeUc9qsyz3|kT*$oZvH@4hQ?H)W@^vZn_VAvCgs_1c-gn=VU znDG$^BIH;TzIMc~)A|wM^H!O)bTnyb$VrnYU94Gs zxtAes@($A%x5>aqhjTTrz-eRW%KbKQqYvM$^*oSoCit*$QjQd zpb8X_G)vmEQHH+Np-6N^{p-8JR9<0QM&3ZW$^VHlJ$ z`jR8lz&d(ddyA~eWH+8GX(P_NXejqwnHO<9tb+~h!?#-HJv{lJ$Tq&Rya(NODLO8XSk{?+zslH3d34Je3Y=G!HrbP%m8#r z+Xr;ROtFe!PAy*v?_6xg__I7!ED5aBdJ;`w?7D1YcY?V2wKR>f<)Im-er9J-h@?@# zYa){&AbL=i06SR`N|MH(ahIYvFUq;4sk^Hfrp;u%yB-xn zSX)nfzc#2=2C67LN^cfcR=BpG`97m~G=4x1{pem^NtrCsR`2jVeRc{gX&I`vjb7uRkvm8Qk>Xxku5 zl9mN~kg6lI)bA(7cx-g^-3(#c%`k?@Kw7g_uP}u}LfIF0?fE!E%tjXLB;*%CDjZCw zM~p?|@0 K$HaUgXrfcKf)r;nU3>!O2_TepM8n_*XwCiKW4+{oT6Yf2SYEFGVz~3 zyUCeuhF*a0PE(1}AK7GJodo@UA}htEwIqokEF!-Bw&x0`+gc^gqWAc=nPb+HqBMn> zCwUe_oo+Q~AU6l35KK=F#5SBWdqdFQ)$jjmn&{>)yJ+G^>?Plx*qU=zIthFy5eXN2`n z7A*}%ZnOo?d|zvH?4=z&NGLD#ihP3Ua3wPH0d-iwM!V8b_N5&;u($$x*dyl**Zvu= zDb%iMG*nol>?tzYYG8xD`c<kXnAnB-b85|XmQrgmqq^Ic|5k7w5tQU#sp7==Btx`%#goqL+$k(;-g|J({3r> zM+X!MoE_9y^`$+W&#QgozM~RUL3x+Y(lWyg+VGntGthPpiWAlMmkRU4O6tu;>9ihQJ=T0WINeN|hT0X=&+2w^5u%EehX42{ z@2OslPW8KZBXTfP%sAPonu$Y5m%lOrV7m9HK_*G1EQ)=fDa`ph_Ao2-h8#F|nq5^0}QRe43@=Ok_? z!Y6-!LampkW2DobJEAU8DYN{+l1hq<)^W*yE!ghru_C|B>KD0JhaHP6C$kco+V^>* zE_EQ;dXxyE#3^J{pFbQ0x_z{0eyM1;G4L-Q#FG z`D2?F(5?u)cJ#)mz1lSCB$j2xt$p-+0-CFR*x$leNh_3{U z@xh$F18~Qd=}x^df6PW*A(AHdkx_JpJLEPe; zxeFf0_gI%FtOMLCTJ--UtN1tIoUP);2<+M$04h(_%>|XM3qmd|3>lMz#*{533!lzW zaae>IogQqCNeEx7Lzg{_b&tP3rBcfqw?(l@1|tgs_BM{qo%Wm0;x9=LjuHX2B))P< z3Z`!0RHNUB=HW*ZK8(c($o;*t{}ApD#3=F5q~8a|*~B}nu6%jM`g@Xo{r^Ckk^>Ee ze$TzhYs%t38Vp!%AyR&qew{0y=YIR%KU*0A;Nqyl3ljd`gnj`r|9l)xTF~Ghtm?n7 zD8qv_OVDPj@;}$6^nVE+=>NO;|J~D~+tokx-}}^|t0-~BWR(?!l&(Cpumj!)4Hl2U zk%#{Sn?{R%MPbe-OHHj&tQ3L0Qh=)9F%#YK`3HTY^cTdd$dSlw9Bu)@1}7?fnIC6w z=sMd!=0%(aBk5KnOB3WA9L^1j>9|Xj_(P@JlHdri4VmTtT*3?IxrmE>3;)0ZxF_dB z_*5gVAB7ujf19Xu_6f}TGx?tW{?b1t=bs0tbvc$wLdc5$6Ft?yBEyq`>AxtWU^#awk0BNafj`$+3=#hC4J~2O-;CO@|L-X3cMSLgws#6S)k^=*4He`-Xfu}S zftmh0xcF!NSt)@(|46Bp_@5g-*9F^m>dCm%(tm3N%&f8}Y*!ynfB1Xmf8THn0M}nW zz5yWM2SY=zpRZwB zq6d4vw*rUW7Rdh?Ue%LqJG364VaaIdeVm~14KzT<0ZonGs(zNVE*7poxQLMVV zhxQtOX2j1JeJ}v358w69r-r8rS7T(0lgEEXKKxOCwrD45`)uVXMZ}-FakkbvZI5=oNA=Ejh)XE`&PtYu2+e znt=525eT768Qam1#Fn+xG7HHq7wXFpl9~gkj{o)ohut*p)iMGMOTlNA0H7oi%U-97 z2vqP5K)Enpt}|o`8-DgW2{pJMe;;vZr3HxjMk>$!s%kKe3zj$P6vzq;Ed%$gq!MaD z%`XmdESnryE{rablOI7r?SPgrK+{g(cYFN3g>QD@a3%oWfPUqbu_>r6jL(S@9Qf1v zy{r49-jep<#pOTSUkrzDv4~dR5Dq_gsVTG?!?)}hO!}J&*s7dyv&+H$8G!7*v_nM@ z84ei#WQcgq?_BsZ4{?2$SO%Ee>X^e@t`)CSxdNs1jS%{%7zDt-UVRx*&s*lg+D3qv zTn$#sHwyDKWBBN7JP4ViFKb~Xd*C;bl-GL`$;((jW_8DIIs zLHRv@p~YhZ#V2f<7Hmr;43#vK&VV;dJa8=Oh0-)`T=YVxOJ}IM6ezDeus}_Y*oHq^ z6fzSU6?pP&r_Gyx*zG@P8P&i3b;l$Hkln_O4@VpE%JqPZ5Iq>d2!SUKA{pOh*VCG# zWm>Pou$FRIA+(@;xbmR3Fml#U6Mz4_3Ml8FKpaFz*QXs*p)P{ur;eE>q_on{rn;Xg z->tv7`7qA^17pY2Hb8nk0qmK9&5uSL08i0wJ3XN!qzKBgy;e*V*p#7(np&{{Trl*^ zmZ#A;#TCfHVXPTUPk4^p_iW8NHf!m1s+IsL;_ZYYf1UR%024Q+MVY!ZXjV1c3O`%0 z;aM0Qqv{+u2GaNwz%x+I_o}FNFnXGH@t;MOCccaPErftR7gXB@($v|^jkkST%O};| zN_RDE1Sk>HP{yPX{o0tnV0-gitAV2q)@Nf6T2)%EXG-{^*bZ=QQgT7XCs{v@NM_1J z)0}`uy8l}&{W~Mt3p5F={^E_YcHPb7a~Oi1G+v_3Ip?zQZWC~5NdmG1jmU|&S_Tjd z?Np6)hk+;Pj*tlS_L&f2prKvAwX)D{t}w-ZAau7=KWB((LU91hZW3da2}WJOWC>7d5(w+&GSK_52)T_%gqX-BOq-T5aV9qWVR~eF zniOD*M(47M++$Tzrlk}6UF~8{z+7gi7;WH`K3Z5!4FQPwZVCnE+}iRTPl1C2ylPrf zUkwucfSF#R7Y($vR(XEMO?F($bW^k69*jZ#=o}tfO~gEwjB`qS3;vp0mUjCw1`L&T zuDQ@qQaT2=l+9DxTa>#o3rczfF?6~4XXf#v#fz~qT`GUeg97>%xzIKM5a3g7v?6{% zz6@d_{FS>P_Q8Bij)(Ad9{9=+O72M@GiirNZIjsirl_yp8#4NSG~{4Z#Hd~`jj(0s zOfkhMe%>N2C_fK)O#!hR@2pie=g$}CfOjz=Xx;j^Gk8=>$o4W}(D|s`n^? zLET6L^CXI&taI641nPZekLXLr72t`b8K}R*5!V?^L5~=^TD%X}3o9?8PiYZO(xe|!*RJTzad(@dv1X5LU?=D# zK$yDIrpDZ3lY|(Rl~U&06n47BLjbr7J2!sv(_5(Tv?NQ5VltP+-Dj1@qgbJEXL@3z{DbDI| zbS^FdOt)aGLst%nm(ROBw%S|0YUP!Ww#%b%rUFK z0aIgshLVQnq91hg;RFH8+!g>|M>(ZA!+LB52l2|$y`Bn=_FBN|d7a_5j^$~D9njWi z=ALkYX2%ize2aX=4`H^Uj_sv)lMUmT6}y}4NThnnnE^JN=!~EnF!H)R$@JwJ1j(yN z-6QK0useOjjvKKuq~qg%GPlqfZ@F$MMLe9=x6b;gb!>kQrxVTWaa;f)Mn}Z+cP(#% z9aLY?w;n`}?g8x3vI80JZNyEGL;@U?hxd70?G#Avfq=I@d*lOBor*)V_eFFBdiNd_ zSHE2XO!g!tQ+Cbu{j2qeAzm(DyEV23jMG zX$_zhScJ*IluOzVF~H|t_W)L_(n*Z?75@*mh|Z(ezKu$w?0k;38leu_M0+)?xznqi z{$;Jd=S%Ep78-1g)V|hQPLv{O=Hn!yD=_vitA5jVfCritWuoN_=do-31 z+GMxwiOr7G$snt)Jkr{)I8sy|P}Gn%Y)dt`DHbpcDM z!Ff!YOjiZvR}-Mj?eiK?vkmC)ae_U6(&NVFZYQGj71t!eM?&~a?0hp=Ex(M%LH#D# zE9!v*VRwDxPNL<^P_m5L5_kFaJ&<^^T&Z$z7l2|e%YbuJV}TMGill|4`3Y}<}!C@!wBk67rgOTEA`QlH{9rN?dM1Knm7(nca@o{HfTU>*pGKl3& zIufJ8kP7x?)eAy~soe@oJnY89T7zwz;r5geT`SnzYT{POA`qK&RfRb!?hY@JUOi+2 z8=bXrV1L4C+e36TXKJ@q-?n$|-@ml)9Zne=9E(f?*j9&2+~m&%*|1PuE%8ZVju=Lm z=VPnvx0ZHz*A26!_nSttE+b4-MyEbANPGM$9vJs#G#vSqdS{4`&=#>}indAE1OROjadFOx=NeuPEM1vJ@cG3$S%(PLjUHHVX%G8*nf0v*48Obou z`(Boe8BLITdHr7Yw3SHUr?-MJ&Pv`BrGfcbuLCv$vB5i{bSl!fopru@m@rBSz^rB- zBOw{nWX8ftJ_bNF>?=QSI=yvly zU-m4jHy!stJ)4M}+f){6=8eQ4;CfM})jRWtecDqJ3ZwGpjAJd&HVdy8(}QlYQd?|X z%O}QE=^-|}Zydh0ij|pbh%Twr@m?mY}2sUQhVmW{D79caC9zk%z`9t?5snTy6#Q8f;eFQ5VE`S-FNvN}A z!-11bkRfCrU__;uR}GP4typQd37M=wqJ`1+(BLAmJx@SC0o9v{IC&C$h4R(3@c!hT z0=NMjt?JCuN!p-~p<@zTrdhsXgcrGE~6^WMd9#q>^M6%H7Jyv_*EneY2$rvt#PA?QGkasoV1NF1`x0< zyIfb66c~~5B*~P`lS1KLpA{^sAKg;)PM54d`zD!7OX1|@AK;Avmw1CTH7u~j>eYY; zL?Zl>N9HWFO)uW2LecH1#b7Pye`#%XPpLbfLP<68is0UPG$v-L2mv6*6@^bz{C+8* z&{cd}US90ES$6*TOJ}$?AGaM|h>gjlV>h5t!)(3U;Y+H;bBsm-BZqlQQ8Ja;O$PAT zGCF`MCY={gx2I|!D^KYq9%#O9>cWOd9g;DkH$lBrGU=7yf?U z8FBN7=3XzFS#TZ&3xL>c)0%D?9mzQv?wW^V#eC+9fcYlVH7wl=s1f0PM_c!dK02Tn z9&&Hhr1U33^aR?zb2KMR`X-X_{b1b)y+W42$`oGvi*NG1*i;j_(<=5kC`=UIR&^Zt zCYv?rxyQA#wh;AdgRMjcB{EOvA)69j3%#R@~5 zlTrM*%G~rqG1XPC1H9}W^r`D5qiC?|c%*%a19ee zZ;v(>JxX?d9b)?DN4by%l`1j*(oSOta&}oOnA*c#NwIDMG6=tYZrq=2YLlx?QP(eq z@dMxlWY_~1P;X`$dChb6zLu|-e*wb%mDfVWjQWX#iP8@@EfRdG8t$K?*HJO`_R_bt zD0}`pS|WKg@I;TOkM9sGSKJWm3rUQrVV{T0E4tZC!WvPm-WD&uumeVl$+=GlG&r9q^lpP3ZaQo5%t$$uFr> zchRw)g@@D4GN{0*p7diExYT{;l!L$OcvA(g=BWO?IfO&J;Yi6&tSk_nm8-foP?-Of?0E;u6-l7!`HK%%LHk z){P8tgRTKus|R#9HoyVO&PU0@pSmY;hE<*hY+<%?u;gi zo@qB4D)UP1>#oC`x=6VaobkLB1r?KbU@#*8-vlV!(12(j)N2N&;~FF4=TUdFJKKsV-?OfkWs(HoZ@Aa%{sA0g*<^~ zrecv&_o2~b`SQtYt2V*I-LvrmRd1iP1eaWSgT_SF-TfYkPx&Njn3pLdhx9q^oskMftHtZ`148_iB?x;nufZ9fQjux*k9#k;hPK&bo*HXQf zd@c^JmhIjs%*_hH+BG%oBA1RoeDvcz=@uUh8}oMp@;x{W`=P_lq7K@z*dFul&y9~% zPjH#SRP3Bl$Kq~4Pp8*%daXo2SSaGZSASP&uFC*m2WZgBe&USm&c&ZVJAr!-V2I%^ zEzK|Umgo`5Cc7I~2SpOL`u%?D-rMJ`BE?FAORcYl{*2~~hnclsCl75&?ltuL5#L(J zzuu>)yG zFCy_S4lka8IXQ{dN=3fdQt9|?Fs?qjH_e3+4U%n1M2((oEbReWKgJDgK;04J(^G)L`fuG>3YJ{g|-cj%W)1- zi^*)p{Fi0*^TjMk;$a3@W!PIbFhbL=jgR~+?Aw1aF8$|mi^F#=8S!&1G&~~FMPB=S zN`E>2ouLve9B!jnWEa5(4cSe6E}|#taISe?JeA= z>NP&Pubv{`!zqYQCAi0~B7Vcfs*`>~gnMb38keO|mOj#Y>-&T>0e@+B%(HMqNNaI5 zIWokI@DvBz@wk>4_<5B?G0wVzcAFjasx`g-*j z%e|Wna-|I6=kjC4TfV5z6{=cJK%=Tz|A!a5LrO;BOa4ae0^j4 z*edfgEsEiE9vEclsqtULATzblxJ!JHkm%Gp%~l}EEQplB<&DYqAC_(J)5vuy-fxo> zGN6+>P>Wlmbcq?XZv5yA*qFIGutJhr5+)7u2Ur4A5ccs%KhFKw#!Q`hB!IBfV8N<=k zLiM{PLRslod#j}SHjiNhwV*?4ezrH$;O@OHPr5LjX|#C8ePuruk%u;w5*Bd$;%VhI zT=s-j=qTC)&Bs*Ed4$B7k4u;27fdg02s+yMVG!fQavx{{m*W|Bc_ZytSKH+;D-VCQ z5q?BIy|b}71hR_AzVs*H^qL;#SpSgQMFF0wGBu$4kcl0T&~aND>6%6XH}jJPkx+Xp zHw+|9$F)i<3R_*y*^-zJBs_WJqAkNv;Ca+KbEqbgK9a+=Gn(ZOHF;6GdSTD+3@$4Y zN+Pc=)9A{AGV8vqJPuat{tdypD>$?LwvMyJm50_0J%dnCcnz`|Y@YH^OPh9BC13FE zdgpEQaNX&3DG}mU*qC2BV_kbf2#V+ehN@OLroXIB$>S5?#8yG4e=w^At@04e1S6&X zybN7=zX7|<&J_XrZhZyz3(T)1`=tl`s?UbERYWrbIxdAR@>t)e%AI}yOm?;54;Vl3 zj1yMC@Kq+7z&Vr0lAkWo{{?kzO^fOwo#sY7*3UWR=Q5tKuoh(!1uJ}%S@mxa%rs=j zZuuTt5_E#iV#Y`hqasqy>3^IrT7+na3=*zyi=r1eo3OLVrqn>rDc$UI5m`g;bIndx zajH2G7U~^&Od%9~y|nRTGP1?a3em0(eXQOup`cQ*iidx|b2BTLrkc62?*{wvyW2sL zmd)ZzYWdWaut=lS<5pk)D?q#S`aJ)}{~H5#s*%2grNo-TufIRw0nZC*Yz2oKjhyP) zQog!h49Pl=XpGm(_aUX*1b4vumq5gO&HN03;I5e0;^JnDm{rY(-plr8~w zR$UUkpdTbWR~L#(xyhMK)Mv}ndv(5_mmrcMmJ!ekaEfHrS^DKk_@Jhom#S$k0M`Y< z7@g!u-0+VM*Itc?!i1X9_j>Gm9Uc(1b&sGnd&?x!hN1^L6?Do zeXJ=@tsy%)0cc{UkYoavy=nFrmS&a=C|sfE*MECiofoI{x_d`Lsq?)N8N2;ziSUI70dnU=6#(pW))||;?S#0k(`epgOn<7j|LoA+MlTQf*+MzbW5L^AOdeZ5g?|KV8FVu;Uxz^t;;p z808qL$o2!%MH#+8y~z1OH_hYwW-lL_BbQi_>F0<0zUw5(U*A3!u5K{8G4w{6ot=8P zzYb!AVdoDw5A#-RAN~p6`eaVyT0bdfe$W=Z`HOA(vuGlU(ff5F~fFld#y-lQO>1 z$h#E*phqvoV|*-j%LIPgWK&`EucFL#_+k?k3 z>YY63#Rx;E(@P{uUVH5{Cfzp2MbJGVy)Q%fmdx5_G(>IzAr#EG0%q?wkdc0%@0!+Q z;4;_iI(098A+zAZewW|<)+Oj`Ri&fFK#FUrN^c~?rgQCI5KFKu#__JGZ3k_ov0k$a zgPdf^h2KJ$@n5irn24zn(iNHn@c`D~Fl%C_HSc@!DWD|2s^rErDnxX{Fq2Xt^e1kU zzc!F_M>8%A^m&}{)FwGJ7{@%_IcZfe^27(F9qR(u9)~6y(UtKqxB7F|ak9t<1v8f5 zI_}`1aqr%U&qs(SP8_8lQ$reALK_+#E$5burJ?hYpa^qI@~~j#%Oz-_5NO;he^;Ht zXvx$$Vz^8Nfj?N-MFbMK77$(5f2d|GzR76;)fK{(9KutEp!~z89mht)f+^f>J>r{k zh!rYNYW=$fQa_|LxAjoQ_^1wy0V?q`1~K)gQh&m#)l~AYy4EtWyj481GWSrKiV$IA zNZ!p?M70b{{0hghG{v7Q!pVwEr*^@svmJUEQ{XYPAB7=RBhYrktGkVMfDgzik8?$s z8zG~bt(b0T#2!zh(eV5bCgX#u5Y>9JUo#TKdb@m6cM)|D4K~0*BfhGW;#crjrEk%1 zg*0sifuT;{zox)*z(4E4B&HNz44F_q>tw&)uZb=rZk#B-eGWW0tLXUiJ44k?G}=#g zn2@A2e`c8Q`b;h}Puc09N#(Qq4u*4*Qj9l5>#L5?=W;7zYV8_`HApLFoAhm(#OqXh zo6lm@$Z2Mi&$;Y+rpR2nS&SuI9_EvmdQNgau@F>_rt49ISK3WA%2l28G7h`!yFr6! zUBjf;H~w~3t0W~Qg{h*J4*-RlRPIJx2M6)v0=0%BDOVPb;*|*Pp>GOF9dIBoXBCzz z^`V|N;cd4uC1IelB=lZ}u_m6)ECng)uzRN%Z2MC*l%MHZ4g4IYyto8tH+SQC$wJfhKTYx?Ix#;l;FXlc4J%Rbz&psRmDDN87G{ zv_^C0fGJcTgeBZ_>v@|}@`Q59rgHE&=x()E1-X6_9W|*I1+Ls!y#gVsDH^;*?A}g&=UI|H35Puz4mFr6BF$KS9B>vl-fuMA}%O&iPAiTQCs=0a4 z-y>{bmxeZJ9wL*{lcPkR`~}gmM%vl^I^gIEZLBNuc@b1t1> z-tb74^Cv<7Wxm&yc}n?j7v$q4a$b^L3cM=*d9fkbZ?gsq zpKIvEW}95J^BlOZ@FiHQE@yJ?o@!=irZhPvvYqg`_feQ@EPl)s&Hoe?j4gr9(ige+ zRIxK)<@l^^uh?!Q|9Bzu@u%=zH6r%CoM=((Qc3L}r9e2XP{C;5oVtqqwI`Ky2!Y+2 zP$G8i7;#<{O?7dFQyoneN91dave+DKeh$YUJyJwO5T3cdX4jJQF~P26n^bntw2y`WCf|)wRs(YUZ_j5MUPe-Cx~vy_R_egoDGFu0U)(QFX*cOA%U4Qz z>08Z9<=T&3Mp1BW;QfpZHw-G~6ndSyPEGr#Vad5!%%oQ!O59z|k=5E4os?w~E6_l( z*d~v<-P{c{agj|rY|!}Tze4_+Q#J8Sz&k9i0mwrvx1bCR9I|PBEzpX~eZDmNGFO3a zJGIPTRqk_PeZ|INVtcvM5viD-$FAUHXQCk%T&>))^lp`hiT^Owf$_65+|Ks=ZF|Xx z^u}*KD+LShWYafsIb&4=(^xJ|Chn%{>7zmPR6PMS%92%YG(%U5tj8VE)ah9W-ut4U z^aA+(`5!`b82BffTI8cj^S93MrE7{Ux8#;9#hzG<%|(_YGaCU; zd#3DEn`$|mKNlaSIxqqc*D3i9zPiuI$_EPGh+3!wWmn}a37EZxIcvp{npu&UFRHM8 zjePjA5R!am5;(k&@v`uh#v^RwDS7X?QqW4IHrLQqNX2gI5$E_{x%J^5Hl31d`N8*3 zK?xTA48#-(2~A&4Nq3jg? zg0fqVOac?1kbW?K!(=2J$*;{k@<(L-OcjdHBaj z1e3>qv+>BJZpZdl$oElm^L?|iZS#gEONEahe3C;!cL!5aWSebVZ%Jo7ONtT(o|yz6 z(SszVbiuiblxI}qg?pK(_<35`eKh7Rx z!gs~Xon!14GbWN?sZz~p51i@C`NvK(sAPywbsZzsC0kSHX-)zN?`5pIfD+2FZ`( zYv1(#=SO!lK?klvh)h`X&wc;*_gq8~vmli&&i|Q~7;yxiflmC*zlZ1qa$4nr>8(K- zz<)-4_a;OfTm?;b@LxlWc>oeZckE2r|8++M;3}a=p}PMX;y9=;W1x;I{r4TAfU6MD zKK=I)1;KRXcE{HJ_r3h{Mv4|(1%<)vUqk%=Ytl3Ginf5y_3Y7q63+fvcX!{52<-MK z>ruu*&#x-BSBv-5tRqSORT#x9g1k)?LkbwsL`(`69<*rx>*I*2Rxv<1n6Hcfi`wU= Vx4B!;7>5MDrenderer()).boundingBox()); - newOverflowClip.moveBy(offset); - newOverflowClip.setAffectedByRadius(renderer().style().border().hasBorderRadius()); - clipRects.setOverflowClipRect(intersection(newOverflowClip, clipRects.overflowClipRect())); - if (renderer().canContainAbsolutelyPositionedObjects()) - clipRects.setPosClipRect(intersection(newOverflowClip, clipRects.posClipRect())); - if (renderer().canContainFixedPositionObjects()) - clipRects.setFixedClipRect(intersection(newOverflowClip, clipRects.fixedClipRect())); + + if (!newOverflowClip.isInfinite()) { + if (needsTransform) + newOverflowClip = LayoutRect(renderer().localToContainerQuad(FloatRect(newOverflowClip.rect()), &clipRectsContext.rootLayer->renderer()).boundingBox()); + newOverflowClip.moveBy(offset); + newOverflowClip.setAffectedByRadius(renderer().style().border().hasBorderRadius()); + clipRects.setOverflowClipRect(intersection(newOverflowClip, clipRects.overflowClipRect())); + if (renderer().canContainAbsolutelyPositionedObjects()) + clipRects.setPosClipRect(intersection(newOverflowClip, clipRects.posClipRect())); + if (renderer().canContainFixedPositionObjects()) + clipRects.setFixedClipRect(intersection(newOverflowClip, clipRects.fixedClipRect())); + } } if (renderer().hasClip()) { if (CheckedPtr box = dynamicDowncast(renderer())) { @@ -5364,9 +5367,11 @@ ClipRect RenderLayer::calculateForegroundRect(const ClipRectsContext& clipRectsC // This layer establishes a clip of some kind. if (this != clipRectsContext.rootLayer || clipRectsContext.respectOverflowClip()) { - auto overflowClipRect = rendererOverflowClipRect(toLayoutPoint(offsetFromRoot), clipRectsContext.overlayScrollbarSizeRelevancy()); - foregroundRect.intersect(overflowClipRect); - foregroundRect.setAffectedByRadius(true); + auto overflowClipRect = rendererOverflowClipRectForPainting(toLayoutPoint(offsetFromRoot), clipRectsContext.overlayScrollbarSizeRelevancy()); + if (!overflowClipRect.isInfinite()) { + foregroundRect.intersect(overflowClipRect); + foregroundRect.setAffectedByRadius(true); + } return foregroundRect; } diff --git a/Source/WebCore/rendering/RenderLayer.h b/Source/WebCore/rendering/RenderLayer.h index 01a8d1a0ed14..e1c61c7c823b 100644 --- a/Source/WebCore/rendering/RenderLayer.h +++ b/Source/WebCore/rendering/RenderLayer.h @@ -1214,12 +1214,12 @@ class RenderLayer final : public UniquelyOwned { return { }; } - LayoutRect rendererOverflowClipRect(const LayoutPoint& location, OverlayScrollbarSizeRelevancy relevancy) const + LayoutRect rendererOverflowClipRectForPainting(const LayoutPoint& location, OverlayScrollbarSizeRelevancy relevancy) const { if (auto* box = dynamicDowncast(renderer())) return box->overflowClipRect(location, relevancy); if (auto* svgModelObject = dynamicDowncast(renderer())) - return svgModelObject->overflowClipRect(location, relevancy); + return svgModelObject->overflowClipRectForPainting(location, relevancy); return { }; } diff --git a/Source/WebCore/rendering/RenderLayerSVGAdditions.cpp b/Source/WebCore/rendering/RenderLayerSVGAdditions.cpp index 7ee9652a5763..c1f038f3b17e 100644 --- a/Source/WebCore/rendering/RenderLayerSVGAdditions.cpp +++ b/Source/WebCore/rendering/RenderLayerSVGAdditions.cpp @@ -433,10 +433,10 @@ bool RenderLayer::appendChildrenInDOMOrderForSVG(RenderElement& parent, LayoutSi continue; } - // Paint a non-layer child that has a clip-path or a mask together with its whole subtree as one - // unit, so the clip or mask covers all of it. Splitting the container apart would move its - // descendants into the parent's z-order list and paint them outside the clip or mask. - if (child->hasClipPath() || child->hasMask()) { + // Paint a non-layer child that clips its subtree together with that subtree as one unit, so the + // clip covers all of it. Splitting it apart would move its descendants into the parent's + // z-order list and paint them outside the clip. + if (RenderSVGModelObject::clipsSubtree(child.get())) { allChildren.append(SVGPaintOrderLayerItem::makeAtomic(child.get(), ancestorOffset)); hasIndependentlyPaintedDescendant = true; continue; @@ -493,6 +493,7 @@ bool RenderLayer::appendChildrenInDOMOrderForSVG(RenderElement& parent, LayoutSi size_t startIndex = allChildren.size(); bool subtreeHasIndependentlyPaintedDescendant = appendChildrenInDOMOrderForSVG(child.get(), childOffset, anyNonZeroZIndex); if (subtreeHasIndependentlyPaintedDescendant) { + ASSERT(!RenderSVGModelObject::clipsSubtree(child.get())); allChildren.append(SVGPaintOrderLayerItem::makeOutlineOnly(child.get(), ancestorOffset)); hasIndependentlyPaintedDescendant = true; } else { diff --git a/Source/WebCore/rendering/svg/RenderSVGContainer.cpp b/Source/WebCore/rendering/svg/RenderSVGContainer.cpp index b7de48bac55b..edef5b6fe592 100644 --- a/Source/WebCore/rendering/svg/RenderSVGContainer.cpp +++ b/Source/WebCore/rendering/svg/RenderSVGContainer.cpp @@ -181,8 +181,10 @@ void RenderSVGContainer::paint(PaintInfo& paintInfo, const LayoutPoint& paintOff GraphicsContextStateSaver stateSaver(childPaintInfo.context()); // For layer-backed containers, clipping is handled by RenderLayer::calculateClipRects(). - if (isRenderSVGViewportContainer() && SVGRenderSupport::isOverflowHidden(*this)) - childPaintInfo.context().clip(FloatRect(overflowClipRect(adjustedPaintOffset))); + if (isRenderSVGViewportContainer() && SVGRenderSupport::isOverflowHidden(*this)) { + if (auto clipRect = overflowClipRectForPainting(adjustedPaintOffset); !clipRect.isInfinite()) + childPaintInfo.context().clip(FloatRect(clipRect)); + } childPaintInfo.updateSubtreePaintRootForChildren(this); for (CheckedRef child : childrenOfType(*this)) { diff --git a/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp b/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp index ad2608d601df..54bb3a690854 100644 --- a/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp +++ b/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp @@ -77,7 +77,7 @@ bool RenderSVGModelObject::requiresLayer() const return true; if (requiresLayerForSVGIntrinsicReasons()) return true; - if ((hasClipPath() || hasMask()) && isRenderSVGContainer()) + if (clipsSubtree(*this) && isRenderSVGContainer()) return true; // Other renderers inside a resource container are not composited, so the transformed-container rule // below (which exists only to expose the induced transform to RenderLayerCompositor) is unneeded. @@ -119,6 +119,23 @@ LayoutRect RenderSVGModelObject::overflowClipRect(const LayoutPoint&, OverlayScr return LayoutRect(); } +LayoutRect RenderSVGModelObject::overflowClipRectForPainting(const LayoutPoint& location, OverlayScrollbarSizeRelevancy relevancy, PaintPhase phase) const +{ + auto clipRect = overflowClipRect(location, relevancy, phase); + if (clipRect.isEmpty() || clipRect.isInfinite()) + return clipRect; + + if (!m_cachedVisualOverflowRect) + updateCachedVisualOverflowRect(); + + auto contentRect = m_cachedVisualOverflowRect->repaintBoundingBox; + contentRect.moveBy(location); + if (clipRect.contains(contentRect)) + return LayoutRect::infiniteRect(); + + return clipRect; +} + auto RenderSVGModelObject::localRectsForRepaint(RepaintOutlineBounds repaintOutlineBounds) const -> RepaintRects { if (isInsideEntirelyHiddenLayer()) diff --git a/Source/WebCore/rendering/svg/RenderSVGModelObject.h b/Source/WebCore/rendering/svg/RenderSVGModelObject.h index 553b9c51ff78..8c7c95bafb85 100644 --- a/Source/WebCore/rendering/svg/RenderSVGModelObject.h +++ b/Source/WebCore/rendering/svg/RenderSVGModelObject.h @@ -54,6 +54,8 @@ class RenderSVGModelObject : public RenderLayerModelObject { bool requiresLayer() const override; + static bool clipsSubtree(const RenderElement&); // Defined in RenderSVGModelObjectInlines.h. + void styleDidChange(Style::Difference, const Style::ComputedStyle* oldStyle) override; static bool checkIntersection(RenderElement*, const FloatRect&); @@ -78,16 +80,30 @@ class RenderSVGModelObject : public RenderLayerModelObject { LayoutRect borderBoxRectEquivalent() const { return { LayoutPoint(), m_layoutRect.size() }; } LayoutRect contentBoxRectEquivalent() const { return borderBoxRectEquivalent(); } LayoutRect frameRectEquivalent() const { return m_layoutRect; } + LayoutSize locationOffsetEquivalent() const { return toLayoutSize(currentSVGLayoutLocation()); } + LayoutRect visualOverflowRectEquivalent() const { if (!m_cachedVisualOverflowRect) - m_cachedVisualOverflowRect = SVGBoundingBoxComputation::computeVisualOverflowRect(*this); - return *m_cachedVisualOverflowRect; + updateCachedVisualOverflowRect(); + return m_cachedVisualOverflowRect->repaintBoundingBoxClippedToViewport; } - std::optional cachedVisualOverflowRectIfAvailable() const { return m_cachedVisualOverflowRect; } - void updateCachedVisualOverflowRect() { m_cachedVisualOverflowRect = SVGBoundingBoxComputation::computeVisualOverflowRect(*this); } - LayoutSize locationOffsetEquivalent() const { return toLayoutSize(currentSVGLayoutLocation()); } + std::optional cachedVisualOverflowRectIfAvailable() const + { + if (!m_cachedVisualOverflowRect) + return std::nullopt; + return m_cachedVisualOverflowRect->repaintBoundingBoxClippedToViewport; + } + + void updateCachedVisualOverflowRect() const + { + auto repaintBoundingBox = SVGBoundingBoxComputation::computeVisualOverflowRectIgnoringViewportClip(*this); + auto repaintBoundingBoxClippedToViewport = repaintBoundingBox; + if (hasNonVisibleOverflow()) + repaintBoundingBoxClippedToViewport.intersect(overflowClipRect(LayoutPoint())); + m_cachedVisualOverflowRect = CachedVisualOverflowRects { repaintBoundingBoxClippedToViewport, repaintBoundingBox }; + } bool hasVisualOverflow() const { return !borderBoxRectEquivalent().contains(visualOverflowRectEquivalent()); } @@ -95,7 +111,8 @@ class RenderSVGModelObject : public RenderLayerModelObject { LayoutPoint topLeftLocationEquivalent() const { return currentSVGLayoutLocation(); } LayoutRect borderBoxRectInFragmentEquivalent(RenderFragmentContainer*, RenderBox::RenderBoxFragmentInfoFlags = RenderBox::RenderBoxFragmentInfoFlags::CacheRenderBoxFragmentInfo) const { return borderBoxRectEquivalent(); } virtual LayoutRect overflowClipRect(const LayoutPoint& location, OverlayScrollbarSizeRelevancy = OverlayScrollbarSizeRelevancy::IgnoreOverlayScrollbarSize, PaintPhase = PaintPhase::BlockBackground) const; - LayoutRect overflowClipRectForChildLayers(const LayoutPoint& location, OverlayScrollbarSizeRelevancy relevancy) { return overflowClipRect(location, relevancy); } + LayoutRect overflowClipRectForPainting(const LayoutPoint& location, OverlayScrollbarSizeRelevancy = OverlayScrollbarSizeRelevancy::IgnoreOverlayScrollbarSize, PaintPhase = PaintPhase::BlockBackground) const; + LayoutRect overflowClipRectForChildLayers(const LayoutPoint& location, OverlayScrollbarSizeRelevancy relevancy) { return overflowClipRectForPainting(location, relevancy); } Path computeClipPathGeometry() const; void computeClipContentTransform(AffineTransform&) const; @@ -129,7 +146,11 @@ class RenderSVGModelObject : public RenderLayerModelObject { // intersection, this return value allows distinguishing between no intersection and zero-area intersection. bool applyCachedClipAndScrollPosition(RepaintRects&, const RenderLayerModelObject* container, const VisibleRectContext&) const final; - mutable std::optional m_cachedVisualOverflowRect; + struct CachedVisualOverflowRects { + LayoutRect repaintBoundingBoxClippedToViewport; + LayoutRect repaintBoundingBox; + }; + mutable std::optional m_cachedVisualOverflowRect; void updateLayerTransform() override; diff --git a/Source/WebCore/rendering/svg/RenderSVGModelObjectInlines.h b/Source/WebCore/rendering/svg/RenderSVGModelObjectInlines.h index 347ad8dab62e..031dfa28f312 100644 --- a/Source/WebCore/rendering/svg/RenderSVGModelObjectInlines.h +++ b/Source/WebCore/rendering/svg/RenderSVGModelObjectInlines.h @@ -25,11 +25,17 @@ #pragma once +#include "RenderElementStyleInlines.h" #include "RenderSVGModelObject.h" #include "SVGElement.h" namespace WebCore { +inline bool RenderSVGModelObject::clipsSubtree(const RenderElement& renderer) +{ + return renderer.hasClipPath() || renderer.hasMask(); +} + inline SVGElement& RenderSVGModelObject::element() const { return downcast(nodeForNonAnonymous()); diff --git a/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.cpp b/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.cpp index 9a5d363e2631..39e3c9db22d7 100644 --- a/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.cpp +++ b/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.cpp @@ -208,6 +208,9 @@ FloatRect SVGBoundingBoxComputation::handleRootOrContainer(const SVGBoundingBoxC FloatRect box; bool boxValid = false; + auto optionsForChildren = options; + optionsForChildren.remove(DecorationOption::IgnoreViewportClip); + // 2. Let parent be the container element if it is one, or the root of the "use" element's shadow tree otherwise. // 3. For each descendant graphics element child of parent: @@ -221,7 +224,7 @@ FloatRect SVGBoundingBoxComputation::handleRootOrContainer(const SVGBoundingBoxC continue; SVGBoundingBoxComputation childBoundingBoxComputation(child); - auto childBox = childBoundingBoxComputation.computeDecoratedBoundingBox(options); + auto childBox = childBoundingBoxComputation.computeDecoratedBoundingBox(optionsForChildren); if (options.contains(DecorationOption::OverrideBoxWithFilterBoxForChildren) && is(child)) { DecorationOptions optionsForChild = { DecorationOption::OverrideBoxWithFilterBox }; if (options.contains(DecorationOption::CalculateFastRepaintRect)) @@ -249,7 +252,7 @@ FloatRect SVGBoundingBoxComputation::handleRootOrContainer(const SVGBoundingBoxC // system space that contains the intersection of box and the rectangle specified by clip. (TODO!) adjustBoxForClippingAndEffects(options, box, { DecorationOption::OverrideBoxWithFilterBox }); - if (options.contains(DecorationOption::IncludeClippers) && m_renderer->hasNonVisibleOverflow()) { + if (options.contains(DecorationOption::IncludeClippers) && !options.contains(DecorationOption::IgnoreViewportClip) && m_renderer->hasNonVisibleOverflow()) { ASSERT(is(m_renderer) || is(m_renderer) || is(m_renderer)); LayoutRect overflowClipRect; @@ -334,17 +337,12 @@ void SVGBoundingBoxComputation::adjustBoxForClippingAndEffects(const SVGBounding box.inflate(m_renderer->outlineStyleForRepaint().usedOutlineSize(m_renderer->outlineStyleForRepaint().usedZoomForLength(), m_renderer->outlineStyleForRepaint().deviceScaleFactor())); } -LayoutRect SVGBoundingBoxComputation::computeVisualOverflowRect(const RenderLayerModelObject& renderer) +static LayoutRect computeVisualOverflowRectWithOptions(const RenderLayerModelObject& renderer, SVGBoundingBoxComputation::DecorationOptions extraOptions) { - // Visual overflow must include descendant transforms: a non-layer transformed descendant paints - // directly into this renderer, so its transformed bounds belong here. (The transform-ignored - // variant is reserved for objectBoundingBoxWithoutTransformations, which defines the SVG layout - // location and must stay flattened.) The result is expressed relative to nominalSVGLayoutLocation(), - // so a container whose content is shifted by a descendant transform is bounded where it paints. - DecorationOptions options = repaintBoundingBoxDecoration | DecorationOption::IncludeOutline; + auto options = SVGBoundingBoxComputation::repaintBoundingBoxDecoration | SVGBoundingBoxComputation::DecorationOption::IncludeOutline | extraOptions; if (is(renderer)) - options = options | DecorationOption::UseFilterBoxOnEmptyRect; - auto decoratedBoundingBox = computeDecoratedBoundingBox(renderer, options); + options = options | SVGBoundingBoxComputation::DecorationOption::UseFilterBoxOnEmptyRect; + auto decoratedBoundingBox = SVGBoundingBoxComputation::computeDecoratedBoundingBox(renderer, options); if (decoratedBoundingBox.isEmpty()) return { }; @@ -353,4 +351,14 @@ LayoutRect SVGBoundingBoxComputation::computeVisualOverflowRect(const RenderLaye return visualOverflowRect; } +LayoutRect SVGBoundingBoxComputation::computeVisualOverflowRect(const RenderLayerModelObject& renderer) +{ + return computeVisualOverflowRectWithOptions(renderer, { }); +} + +LayoutRect SVGBoundingBoxComputation::computeVisualOverflowRectIgnoringViewportClip(const RenderLayerModelObject& renderer) +{ + return computeVisualOverflowRectWithOptions(renderer, DecorationOption::IgnoreViewportClip); +} + } diff --git a/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.h b/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.h index 8cb85c70808c..4bc8979b5096 100644 --- a/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.h +++ b/Source/WebCore/rendering/svg/SVGBoundingBoxComputation.h @@ -44,7 +44,8 @@ class SVGBoundingBoxComputation { OverrideBoxWithFilterBox = 1 << 7, /* WebKit extension - internal */ OverrideBoxWithFilterBoxForChildren = 1 << 8, /* WebKit extension - internal */ CalculateFastRepaintRect = 1 << 9, /* WebKit extension - internal */ - UseFilterBoxOnEmptyRect = 1 << 10 /* WebKit extension - internal */ + UseFilterBoxOnEmptyRect = 1 << 10, /* WebKit extension - internal */ + IgnoreViewportClip = 1 << 11 /* WebKit extension - internal */ }; using DecorationOptions = OptionSet; @@ -74,6 +75,7 @@ class SVGBoundingBoxComputation { } static LayoutRect computeVisualOverflowRect(const RenderLayerModelObject&); + static LayoutRect computeVisualOverflowRectIgnoringViewportClip(const RenderLayerModelObject&); // Recompute the transform-dependent bounding boxes shared by RenderSVGContainer and // RenderSVGRoot after a descendant transform changed outside layout. Pass objectBoundingBoxValid From 9ec86f1d4f78ebf4ae2dd140de2384723101175f Mon Sep 17 00:00:00 2001 From: Karl Rackler Date: Fri, 28 Aug 2026 08:35:45 -0700 Subject: [PATCH 038/103] [Gardening]: (New Test(319793@main): [macOS Debug] imported/w3c/web-platform-tests/css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/baseline/row-grid-lanes-item-baseline-subgrid-001.html is a constant CRASH) https://bugs.webkit.org/show_bug.cgi?id=322847 rdar://186086174 Unreviewed test gardening. Add test expectation. * LayoutTests/platform/mac-wk2/TestExpectations: Canonical link: https://commits.webkit.org/320046@main --- LayoutTests/platform/mac-wk2/TestExpectations | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LayoutTests/platform/mac-wk2/TestExpectations b/LayoutTests/platform/mac-wk2/TestExpectations index 027767521231..3aff1b45e914 100644 --- a/LayoutTests/platform/mac-wk2/TestExpectations +++ b/LayoutTests/platform/mac-wk2/TestExpectations @@ -2484,4 +2484,6 @@ webkit.org/b/321323 [ Tahoe+ ] http/tests/websocket/tests/hybi/inspector/before- [ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-playback-temporary.https.html [ Failure Crash ] [ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-setmediakeys-again-after-playback.https.html [ Failure Crash ] [ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-setmediakeys-again-after-resetting-src.https.html [ Failure Crash ] -[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-waiting-for-a-key.https.html [ Failure Crash ] \ No newline at end of file +[ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-waiting-for-a-key.https.html [ Failure Crash ] + +webkit.org/b/322847 [ Debug ] imported/w3c/web-platform-tests/css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/baseline/row-grid-lanes-item-baseline-subgrid-001.html [ Crash ] \ No newline at end of file From cbd439246453ce331023b253f8bfd8c18ab2511a Mon Sep 17 00:00:00 2001 From: Karl Rackler Date: Fri, 28 Aug 2026 08:46:00 -0700 Subject: [PATCH 039/103] [Gardening]: (New Test(319793@main): [macOS Debug] imported/w3c/web-platform-tests/css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/baseline/row-grid-lanes-item-baseline-subgrid-001.html is a constant CRASH) https://bugs.webkit.org/show_bug.cgi?id=322847 rdar://186086174 Unreviewed test gardening. Add test expectations for row-grid-lanes-item-baseline-subgrid-004.html and row-grid-lanes-item-baseline-subgrid-005.html, which hit the same GridTrackSizingAlgorithm assertion on macOS Debug as -001. * LayoutTests/platform/mac-wk2/TestExpectations: Canonical link: https://commits.webkit.org/320047@main --- LayoutTests/platform/mac-wk2/TestExpectations | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LayoutTests/platform/mac-wk2/TestExpectations b/LayoutTests/platform/mac-wk2/TestExpectations index 3aff1b45e914..76313f082be4 100644 --- a/LayoutTests/platform/mac-wk2/TestExpectations +++ b/LayoutTests/platform/mac-wk2/TestExpectations @@ -2486,4 +2486,6 @@ webkit.org/b/321323 [ Tahoe+ ] http/tests/websocket/tests/hybi/inspector/before- [ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-setmediakeys-again-after-resetting-src.https.html [ Failure Crash ] [ Tahoe Debug ] imported/w3c/web-platform-tests/encrypted-media/drm-mp4-waiting-for-a-key.https.html [ Failure Crash ] -webkit.org/b/322847 [ Debug ] imported/w3c/web-platform-tests/css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/baseline/row-grid-lanes-item-baseline-subgrid-001.html [ Crash ] \ No newline at end of file +webkit.org/b/322847 [ Debug ] imported/w3c/web-platform-tests/css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/baseline/row-grid-lanes-item-baseline-subgrid-001.html [ Crash ] +webkit.org/b/322847 [ Debug ] imported/w3c/web-platform-tests/css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/baseline/row-grid-lanes-item-baseline-subgrid-004.html [ Crash ] +webkit.org/b/322847 [ Debug ] imported/w3c/web-platform-tests/css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/baseline/row-grid-lanes-item-baseline-subgrid-005.html [ Crash ] \ No newline at end of file From 261bcb42d83f2838cb8245675ffd6e53eebf55bc Mon Sep 17 00:00:00 2001 From: Keith Miller Date: Fri, 28 Aug 2026 08:47:51 -0700 Subject: [PATCH 040/103] [Wasm] Delegate should widen types like End https://bugs.webkit.org/show_bug.cgi?id=318755 rdar://181457816 Reviewed by Yusuke Suzuki. The legacy Delegate wasm bytecode is essentially shorthand for: `Rethrow; End`. So like other merge points it needs to widen the types on the expression stack. To help avoid this problem in the future add a new helper `endBlockAndCheckResultTypes`, which ensures the types are appropriately widened. Test: JSTests/wasm/stress/delegate-widens-result-type-to-signature.js Originally-landed-as: 305413.1096@safari-7624.5-branch (db3010593c27). rdar://185366253 Canonical link: https://commits.webkit.org/320048@main --- ...elegate-widens-result-type-to-signature.js | 74 +++++++++++++++++++ .../JavaScriptCore/wasm/WasmFunctionParser.h | 44 ++++++----- 2 files changed, 95 insertions(+), 23 deletions(-) create mode 100644 JSTests/wasm/stress/delegate-widens-result-type-to-signature.js diff --git a/JSTests/wasm/stress/delegate-widens-result-type-to-signature.js b/JSTests/wasm/stress/delegate-widens-result-type-to-signature.js new file mode 100644 index 000000000000..20b86f82b44a --- /dev/null +++ b/JSTests/wasm/stress/delegate-widens-result-type-to-signature.js @@ -0,0 +1,74 @@ +import * as assert from "../assert.js"; + +function uleb128(n) { const r = []; do { let b = n & 0x7f; n >>>= 7; if (n) b |= 0x80; r.push(b); } while (n); return r; } +function encodeString(s) { const b = []; for (let i = 0; i < s.length; i++) b.push(s.charCodeAt(i)); return [...uleb128(b.length), ...b]; } +function section(id, content) { return [id, ...uleb128(content.length), ...content]; } + +function buildModule() { + const typeSection = section(1, [ + 3, + 0x5F, 0x01, 0x7E, 0x01, // type 0: struct { i64 mut } + 0x60, 0x03, 0x7F, 0x6F, 0x64, 0x00, 0x01, 0x7E, // type 1: func (i32, externref, (ref 0)) -> i64 + 0x60, 0x00, 0x01, 0x64, 0x00, // type 2: func () -> (ref 0) + ]); + const funcSection = section(3, [0x02, 0x01, 0x02]); + const exportSection = section(7, [0x02, + ...encodeString("test"), 0x00, 0x00, + ...encodeString("make"), 0x00, 0x01]); + + // (func $test (param $cond i32) (param $ext externref) (param $s (ref 0)) (result i64) + // try (result i64) ;; outer: delegate target + // try (result anyref) ;; inner + // local.get $ext + // any.convert_extern ;; -> anyref (NaN-boxed JS number) + // local.get $cond + // br_if 0 ;; carry the anyref to the inner continuation + // drop + // local.get $s ;; fallthrough: (ref 0), a subtype of anyref + // delegate 0 ;; terminates inner try; result MUST widen to anyref + // ref.cast (ref 0) ;; must NOT elide IsCell / IsWasmGCObject checks + // struct.get 0 0 + // catch_all + // i64.const 0 + // end) + const body0 = [ + 0x00, + 0x06, 0x7E, // try (result i64) + 0x06, 0x6E, // try (result anyref) + 0x20, 0x01, // local.get 1 + 0xFB, 0x1A, // any.convert_extern + 0x20, 0x00, // local.get 0 + 0x0D, 0x00, // br_if 0 + 0x1A, // drop + 0x20, 0x02, // local.get 2 + 0x18, 0x00, // delegate 0 + 0xFB, 0x16, 0x00, // ref.cast (ref 0) + 0xFB, 0x02, 0x00, 0x00, // struct.get 0 0 + 0x19, // catch_all + 0x42, 0x00, // i64.const 0 + 0x0B, // end (outer try) + 0x0B, // end (func) + ]; + // (func $make (result (ref 0)) i64.const 0x1234 struct.new 0) + const body1 = [0x00, 0x42, 0xB4, 0x24, 0xFB, 0x00, 0x00, 0x0B]; + const codeSection = section(10, [0x02, + ...uleb128(body0.length), ...body0, + ...uleb128(body1.length), ...body1]); + return new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + ...typeSection, ...funcSection, ...exportSection, ...codeSection]); +} + +const bytes = buildModule(); +assert.truthy(WebAssembly.validate(bytes)); +const instance = new WebAssembly.Instance(new WebAssembly.Module(bytes)); +const struct = instance.exports.make(); + +for (let i = 0; i < wasmTestLoopCount; ++i) { + // cond == 0: br_if not taken; the try body's (ref 0) fallthrough survives the + // delegate. Widened to anyref, ref.cast succeeds and struct.get reads the field. + assert.eq(instance.exports.test(0, null, struct), 0x1234n); + // cond == 1: br_if delivers an anyref-wrapped JS number to the inner continuation. + // The post-delegate value is statically anyref, so ref.cast must perform the full + // runtime check and trap rather than dereference the non-cell value. + assert.throws(() => instance.exports.test(1, 1.5, struct), WebAssembly.RuntimeError, "ref.cast failed to cast reference to target heap type"); +} diff --git a/Source/JavaScriptCore/wasm/WasmFunctionParser.h b/Source/JavaScriptCore/wasm/WasmFunctionParser.h index 4361ff3e3bd9..1e585656ef2d 100644 --- a/Source/JavaScriptCore/wasm/WasmFunctionParser.h +++ b/Source/JavaScriptCore/wasm/WasmFunctionParser.h @@ -254,6 +254,7 @@ class FunctionParser : public Parser, public FunctionParserTypes::checkBlockFallthrough(const ControlType& controlDa return { }; } +template +auto FunctionParser::endBlockAndCheckResultTypes(ControlEntry& entry) -> PartialResult +{ + // Widen each result to the block signature type before ending the block. + // FIXME: mutating the expression stack for the block result is effectful, but there's no + // better API yet. See https://bugs.webkit.org/show_bug.cgi?id=164353 + WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(entry.controlData, MergePoint)); + const uint32_t parentBegin = parentEntryBegin(); + auto enclosedStack = m_expressionStack.mutableSpan().subspan(parentBegin); + // We should avoid adding other callsites of endBlock. Since a new block is a sign of a + // merge point and it would be a security bug to fail to widen the types. + WASM_TRY_ADD_TO_CONTEXT(endBlock(entry, enclosedStack)); + m_currentStackBegin = parentBegin; + return { }; +} + template auto FunctionParser::parseArrayTypeDefinition(ASCIILiteral operation, bool isNullable, TypeSignatureIndex& typeIndex, FieldType& elementType, Type& arrayRefType) -> PartialResult { @@ -3724,13 +3741,8 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) WASM_VALIDATOR_FAIL_IF(!ControlType::isTry(targetData) && !ControlType::isTopLevel(targetData), "delegate target isn't a try or the top level block"); WASM_TRY_ADD_TO_CONTEXT(addDelegate(targetData, controlEntry.controlData)); - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); - - const uint32_t parentBegin = parentEntryBegin(); - auto enclosedStack = m_expressionStack.mutableSpan().subspan(parentBegin); - WASM_TRY_ADD_TO_CONTEXT(endBlock(controlEntry, enclosedStack)); - - m_currentStackBegin = parentBegin; + // Unlike the sibling catch/catch_all arms, delegate ends the try block, so it widens results. + WASM_FAIL_IF_HELPER_FAILS(endBlockAndCheckResultTypes(controlEntry)); resetLocalInitStackToHeight(controlEntry.localInitStackHeight); return { }; } @@ -3865,16 +3877,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) m_expressionStack.shrink(m_currentStackBegin); m_expressionStack.append(data.elseBlockStack.span()); } - // FIXME: endBlock may modify the expressionStack slice for the result of the block. - // That's a little too effectful but we don't have a better API right now. - // see: https://bugs.webkit.org/show_bug.cgi?id=164353 - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(data.controlData, MergePoint)); - - const uint32_t parentBegin = parentEntryBegin(); - auto enclosedStack = m_expressionStack.mutableSpan().subspan(parentBegin); - WASM_TRY_ADD_TO_CONTEXT(endBlock(data, enclosedStack)); - - m_currentStackBegin = parentBegin; + WASM_FAIL_IF_HELPER_FAILS(endBlockAndCheckResultTypes(data)); if (!ControlType::isTopLevel(data.controlData)) resetLocalInitStackToHeight(data.localInitStackHeight); return { }; @@ -4070,12 +4073,7 @@ auto FunctionParser::parseUnreachableExpression() -> PartialResult WASM_TRY_ADD_TO_CONTEXT(addElseToUnreachable(data.controlData)); m_expressionStack.shrink(m_currentStackBegin); m_expressionStack.append(data.elseBlockStack.span()); - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(data.controlData, MergePoint)); - - // Reachable End handling: the combined enclosedStack now lives in - // m_expressionStack[parentBegin..end]. - auto enclosedStack = m_expressionStack.mutableSpan().subspan(parentBegin); - WASM_TRY_ADD_TO_CONTEXT(endBlock(data, enclosedStack)); + WASM_FAIL_IF_HELPER_FAILS(endBlockAndCheckResultTypes(data)); } else { m_expressionStack.shrink(m_currentStackBegin); const auto& sig = data.controlData.signature(); From 31b499148359010dfe933be55130957c3b8848ef Mon Sep 17 00:00:00 2001 From: Chris Dumez Date: Fri, 28 Aug 2026 08:49:23 -0700 Subject: [PATCH 041/103] Unreviewed, fix safer cpp regression from 320030@main https://bugs.webkit.org/show_bug.cgi?id=322841 * Source/WebKit/UIProcess/mac/WebViewImpl.mm: (WebKit::WebViewImpl::tryToSwipeWithEvent): (WebKit::WebViewImpl::scrollWheel): (WebKit::WebViewImpl::nativeMouseEventHandler): Canonical link: https://commits.webkit.org/320049@main --- Source/WebKit/UIProcess/mac/WebViewImpl.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/WebKit/UIProcess/mac/WebViewImpl.mm b/Source/WebKit/UIProcess/mac/WebViewImpl.mm index b1de0c572813..25ee976c434a 100644 --- a/Source/WebKit/UIProcess/mac/WebViewImpl.mm +++ b/Source/WebKit/UIProcess/mac/WebViewImpl.mm @@ -5712,7 +5712,7 @@ static NSPasteboardName NODELETE pasteboardNameForAccessCategory(WebCore::DOMPas bool wasIgnoringPinnedState = gestureController->shouldIgnorePinnedState(); gestureController->setShouldIgnorePinnedState(ignoringPinnedState); - Ref webEvent = NativeWebWheelEvent::create(event, m_view.getAutoreleased()); + Ref webEvent = NativeWebWheelEvent::create(event, m_view.get()); bool handledEvent = gestureController->handleScrollWheelEvent(webEvent); gestureController->setShouldIgnorePinnedState(wasIgnoringPinnedState); @@ -5742,7 +5742,7 @@ static NSPasteboardName NODELETE pasteboardNameForAccessCategory(WebCore::DOMPas updateRefreshControllerForWheelEvent(event); #endif - Ref webEvent = NativeWebWheelEvent::create(event, m_view.getAutoreleased()); + Ref webEvent = NativeWebWheelEvent::create(event, m_view.get()); if (m_allowsBackForwardNavigationGestures && protect(ensureGestureController())->handleScrollWheelEvent(webEvent)) { RELEASE_LOG(MouseHandling, "[pageProxyID=%lld] WebViewImpl::scrollWheel: Gesture controller handled wheel event", m_page->identifier().toUInt64()); @@ -6888,7 +6888,7 @@ static BOOL shouldUseHighlightsForMarkedText(NSAttributedString *string) if (handled) LOG_WITH_STREAM(TextInput, stream << "Event " << [retainedEvent type] << " was handled by text input context"); else { - Ref webEvent = NativeWebMouseEvent::create(retainedEvent.get(), weakThis->m_lastPressureEvent.get(), weakThis->m_view.getAutoreleased(), inputSource, canInitiateDrag); + Ref webEvent = NativeWebMouseEvent::create(retainedEvent.get(), weakThis->m_lastPressureEvent.get(), weakThis->m_view.get(), inputSource, canInitiateDrag); weakThis->m_page->handleMouseEvent(WTF::move(webEvent)); } }]; From 2b438524716502ed500bcac9bc8af5fa4aa7927e Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Fri, 28 Aug 2026 08:52:28 -0700 Subject: [PATCH 042/103] media/media-source/media-source-video-renders.html is constant ImageOnlyfailure https://bugs.webkit.org/show_bug.cgi?id=252322 rdar://105502071 Reviewed by Eric Carlson. Chroma location information was lost during the IPC serialisation. The file stated Centre and so VideoToolbox defaulted to Left instead. Fly-by: CVFieldCount/CVFieldDetail information was lost, we add it. CVPixelAspectRatio was a rational that we converted to floats internally, leading to small innacuracies. We now use integer spacings instead (video in test AR was set as 1.185185...:1 where it should have been 32:27) PlatformVideoColorSpace gaining a chromaLocation member makes the hand-written colorSpace literal in ipc/cocoa/videoEncode.html incomplete, and coreipc.js's serializer throws on a missing member; give it an absent chromaLocation. Test: Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm * LayoutTests/ipc/cocoa/videoEncode.html: * LayoutTests/platform/mac/TestExpectations: * Source/WebCore/Headers.cmake: * Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp: (WebCore::WebCodecsVideoFrame::create): * Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameAlgorithms.cpp: (WebCore::videoFramePickColorSpace): * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/platform/TrackInfo.h: (WebCore::VideoInfo::fieldCount const): (WebCore::VideoInfo::fieldDetail const): * Source/WebCore/platform/cocoa/CoreVideoSoftLink.cpp: * Source/WebCore/platform/cocoa/CoreVideoSoftLink.h: * Source/WebCore/platform/graphics/PlatformVideoChromaLocation.h: Copied from Source/WebCore/platform/graphics/PlatformVideoColorSpace.h. * Source/WebCore/platform/graphics/PlatformVideoColorSpace.cpp: (WebCore::operator<<): (WebCore::overrideVideoColorSpaceAsNeeded): * Source/WebCore/platform/graphics/PlatformVideoColorSpace.h: (WebCore::PlatformVideoColorSpace::isValid const): * Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp: (WebCore::colorSpaceFromFormatDescription): (WebCore::fieldCountFromFormatDescription): (WebCore::fieldDetailFromFormatDescription): * Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h: * Source/WebCore/platform/graphics/cocoa/CMUtilities.mm: (WebCore::createExtensionsDictionary): (WebCore::convertToCMChromaLocation): (WebCore::convertToCMFieldDetail): (WebCore::createFormatDescriptionFromTrackInfo): (WebCore::createVideoInfoFromFormatDescription): (WebCore::attachColorSpaceToPixelBuffer): (WebCore::computeVideoFrameColorSpace): * Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp: (WebCore::LibWebRTCVPXInternalVideoEncoder::encode): (WebCore::LibWebRTCVPXInternalVideoEncoder::OnEncodedImage): * Source/WebCore/platform/mediastream/cocoa/RealtimeOutgoingVideoSourceCocoa.cpp: (WebCore::RealtimeOutgoingVideoSourceCocoa::videoFrameAvailable): * Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCVideoFrameUtilities.cpp: (WebCore::colorSpaceFromLibWebRTCColorSpace): * Source/WebCore/platform/mediastream/libwebrtc/VideoFrameLibWebRTC.cpp: (WebCore::defaultVPXColorSpace): * Source/WebCore/platform/mediastream/libwebrtc/gstreamer/RealtimeOutgoingVideoSourceLibWebRTC.cpp: (WebCore::RealtimeOutgoingVideoSourceLibWebRTC::videoFrameAvailable): * Source/WebKit/GPUProcess/webrtc/LibWebRTCCodecsProxy.mm: (WebKit::LibWebRTCCodecsProxy::createEncoder): * Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in: * Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm: (TestWebKitAPI::TEST(CMUtilities, ChromaLocationRoundTrip)): (TestWebKitAPI::TEST(CMUtilities, AbsentChromaLocationStaysAbsent)): (TestWebKitAPI::TEST(CMUtilities, ChromaLocationSurvivesFormatDescriptionRoundTrip)): (TestWebKitAPI::TEST(CMUtilities, FieldCountAndDetailRoundTrip)): (TestWebKitAPI::TEST(CMUtilities, AbsentFieldInfoStaysAbsent)): (TestWebKitAPI::TEST(CMUtilities, PixelAspectRatioUsesIntegerSpacings)): (TestWebKitAPI::TEST(CMUtilities, SquarePixelAspectRatioEmitsNoExtension)): Canonical link: https://commits.webkit.org/320050@main --- LayoutTests/ipc/cocoa/videoEncode.html | 2 +- LayoutTests/platform/mac/TestExpectations | 2 - Source/WebCore/Headers.cmake | 1 + .../Modules/webcodecs/WebCodecsVideoFrame.cpp | 2 +- .../WebCodecsVideoFrameAlgorithms.cpp | 4 +- .../WebCore/WebCore.xcodeproj/project.pbxproj | 4 + Source/WebCore/platform/TrackInfo.h | 14 ++ .../platform/cocoa/CoreVideoSoftLink.cpp | 15 ++ .../platform/cocoa/CoreVideoSoftLink.h | 30 +++ .../graphics/PlatformVideoChromaLocation.h | 49 +++++ .../graphics/PlatformVideoColorSpace.cpp | 35 +++ .../graphics/PlatformVideoColorSpace.h | 6 +- .../FormatDescriptionUtilities.cpp | 60 ++++++ .../avfoundation/FormatDescriptionUtilities.h | 2 + .../platform/graphics/cocoa/CMUtilities.mm | 115 +++++++++- .../libwebrtc/LibWebRTCVPXVideoEncoder.cpp | 4 +- .../RealtimeOutgoingVideoSourceCocoa.cpp | 2 +- .../LibWebRTCVideoFrameUtilities.cpp | 2 +- .../libwebrtc/VideoFrameLibWebRTC.cpp | 2 +- .../RealtimeOutgoingVideoSourceLibWebRTC.cpp | 2 +- .../GPUProcess/webrtc/LibWebRTCCodecsProxy.mm | 2 +- .../WebCoreArgumentCoders.serialization.in | 22 ++ .../Tests/WebCore/cocoa/CoreMediaUtilities.mm | 204 ++++++++++++++++++ 23 files changed, 557 insertions(+), 24 deletions(-) create mode 100644 Source/WebCore/platform/graphics/PlatformVideoChromaLocation.h diff --git a/LayoutTests/ipc/cocoa/videoEncode.html b/LayoutTests/ipc/cocoa/videoEncode.html index c0e81eaba911..2e44a2b1e730 100644 --- a/LayoutTests/ipc/cocoa/videoEncode.html +++ b/LayoutTests/ipc/cocoa/videoEncode.html @@ -22,7 +22,7 @@ id, width, height, startBitrate:62, maxBitrate:91, minBitrate:5, maxFramerate:720916 }); CoreIPC.GPU.LibWebRTCCodecsProxy.EncodeFrame(0, { - id, buffer:{ time: { timeValue:43, timeScale:79, timeFlags:145 }, mirrored:false, rotation:0, colorSpace: {primaries: {optionalValue: 0}, transfer: {optionalValue: 0}, matrix:{optionalValue: 1}, fullRange: {optionalValue: false}}, buffer:{ alias: { variantType:'WebCore::IntSize', variant : { width, height } } }}, timeStamp:61, duration:{}, shouldEncodeAsKeyFrame:true + id, buffer:{ time: { timeValue:43, timeScale:79, timeFlags:145 }, mirrored:false, rotation:0, colorSpace: {primaries: {optionalValue: 0}, transfer: {optionalValue: 0}, matrix:{optionalValue: 1}, fullRange: {optionalValue: false}, chromaLocation: {}}, buffer:{ alias: { variantType:'WebCore::IntSize', variant : { width, height } } }}, timeStamp:61, duration:{}, shouldEncodeAsKeyFrame:true }); CoreIPC.GPU.LibWebRTCCodecsProxy.ReleaseEncoder(0, { id diff --git a/LayoutTests/platform/mac/TestExpectations b/LayoutTests/platform/mac/TestExpectations index e1b4ac5905db..9221ac191ecd 100644 --- a/LayoutTests/platform/mac/TestExpectations +++ b/LayoutTests/platform/mac/TestExpectations @@ -1935,8 +1935,6 @@ webkit.org/b/258181 [ Debug ] inspector/debugger/async-stack-trace-truncate.html webkit.org/b/236128 imported/w3c/web-platform-tests/html/user-activation/activation-trigger-mouse-right.html [ Skip ] -webkit.org/b/252322 [ X86_64 ] media/media-source/media-source-video-renders.html [ ImageOnlyFailure ] # rdar://180537206 - webkit.org/b/259712 media/media-source/media-source-paint-after-display-none.html [ Skip ] # rdar://110876540 ASSERTION FAILED: firstChild(): [ macOS ] (258183) diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake index 0682097ef19d..fa3788d8d19d 100644 --- a/Source/WebCore/Headers.cmake +++ b/Source/WebCore/Headers.cmake @@ -2685,6 +2685,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS platform/graphics/PlatformTextTrack.h platform/graphics/PlatformTimeRanges.h platform/graphics/PlatformTrackConfiguration.h + platform/graphics/PlatformVideoChromaLocation.h platform/graphics/PlatformVideoColorPrimaries.h platform/graphics/PlatformVideoColorSpace.h platform/graphics/PlatformVideoMatrixCoefficients.h diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp index a635371e5dc0..eaec35155b48 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp @@ -263,7 +263,7 @@ ExceptionOr> WebCodecsVideoFrame::create(ScriptExecutio if (!pixelBuffer) return Exception { ExceptionCode::InvalidStateError, "Buffer has no frame"_s }; - auto videoFrame = VideoFrame::createFromPixelBuffer(pixelBuffer.releaseNonNull(), { PlatformVideoColorPrimaries::Bt709, PlatformVideoTransferCharacteristics::Iec6196621, PlatformVideoMatrixCoefficients::Rgb, true }); + auto videoFrame = VideoFrame::createFromPixelBuffer(pixelBuffer.releaseNonNull(), { .primaries = PlatformVideoColorPrimaries::Bt709, .transfer = PlatformVideoTransferCharacteristics::Iec6196621, .matrix = PlatformVideoMatrixCoefficients::Rgb, .fullRange = true }); if (!videoFrame) return Exception { ExceptionCode::InvalidStateError, "Unable to create frame from buffer"_s }; diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameAlgorithms.cpp b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameAlgorithms.cpp index 0b5ec95978d9..9fe8c2da7324 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameAlgorithms.cpp +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameAlgorithms.cpp @@ -268,9 +268,9 @@ VideoColorSpaceInit videoFramePickColorSpace(const std::optional>; #if ENABLE(ENCRYPTED_MEDIA) using TrackInfoEncryptionData = std::pair>; @@ -147,6 +156,9 @@ struct VideoSpecificInfoData { FloatSize displaySize { }; uint8_t bitDepth { 8 }; PlatformVideoColorSpace colorSpace { }; + // Number of fields per frame: 1 for progressive content, 2 for interlaced. + std::optional fieldCount { }; + std::optional fieldDetail { }; Vector extensionAtoms { }; #if PLATFORM(VISION) @@ -168,6 +180,8 @@ class VideoInfo : public TrackInfo { const FloatSize& displaySize() const LIFETIME_BOUND { return m_data.displaySize; } uint8_t bitDepth() const { return m_data.bitDepth; } const PlatformVideoColorSpace& colorSpace() const LIFETIME_BOUND { return m_data.colorSpace; } + std::optional fieldCount() const { return m_data.fieldCount; } + std::optional fieldDetail() const { return m_data.fieldDetail; } const Vector& extensionAtoms() const LIFETIME_BOUND { return m_data.extensionAtoms; } diff --git a/Source/WebCore/platform/cocoa/CoreVideoSoftLink.cpp b/Source/WebCore/platform/cocoa/CoreVideoSoftLink.cpp index 622057819eb3..cd6bc8cadb4f 100644 --- a/Source/WebCore/platform/cocoa/CoreVideoSoftLink.cpp +++ b/Source/WebCore/platform/cocoa/CoreVideoSoftLink.cpp @@ -96,6 +96,21 @@ SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferYCbCrMatrix_ITU_ SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferPixelAspectRatioKey, CFStringRef) SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferPixelAspectRatioHorizontalSpacingKey, CFStringRef) SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferPixelAspectRatioVerticalSpacingKey, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocationTopFieldKey, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocationBottomFieldKey, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocation_Left, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocation_Center, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocation_TopLeft, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocation_Top, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocation_BottomLeft, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocation_Bottom, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferChromaLocation_DV420, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferFieldCountKey, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferFieldDetailKey, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferFieldDetailTemporalTopFirst, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferFieldDetailTemporalBottomFirst, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferFieldDetailSpatialFirstLineEarly, CFStringRef) +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferFieldDetailSpatialFirstLineLate, CFStringRef) SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferCGColorSpaceKey, CFStringRef) SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, CoreVideo, kCVImageBufferCleanApertureKey, CFStringRef) diff --git a/Source/WebCore/platform/cocoa/CoreVideoSoftLink.h b/Source/WebCore/platform/cocoa/CoreVideoSoftLink.h index d6feca37c86a..f9ef881dbba8 100644 --- a/Source/WebCore/platform/cocoa/CoreVideoSoftLink.h +++ b/Source/WebCore/platform/cocoa/CoreVideoSoftLink.h @@ -169,6 +169,36 @@ SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferPixelAspectRatio #define kCVImageBufferPixelAspectRatioHorizontalSpacingKey get_CoreVideo_kCVImageBufferPixelAspectRatioHorizontalSpacingKeySingleton() SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferPixelAspectRatioVerticalSpacingKey, CFStringRef) #define kCVImageBufferPixelAspectRatioVerticalSpacingKey get_CoreVideo_kCVImageBufferPixelAspectRatioVerticalSpacingKeySingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocationTopFieldKey, CFStringRef) +#define kCVImageBufferChromaLocationTopFieldKey get_CoreVideo_kCVImageBufferChromaLocationTopFieldKeySingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocationBottomFieldKey, CFStringRef) +#define kCVImageBufferChromaLocationBottomFieldKey get_CoreVideo_kCVImageBufferChromaLocationBottomFieldKeySingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocation_Left, CFStringRef) +#define kCVImageBufferChromaLocation_Left get_CoreVideo_kCVImageBufferChromaLocation_LeftSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocation_Center, CFStringRef) +#define kCVImageBufferChromaLocation_Center get_CoreVideo_kCVImageBufferChromaLocation_CenterSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocation_TopLeft, CFStringRef) +#define kCVImageBufferChromaLocation_TopLeft get_CoreVideo_kCVImageBufferChromaLocation_TopLeftSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocation_Top, CFStringRef) +#define kCVImageBufferChromaLocation_Top get_CoreVideo_kCVImageBufferChromaLocation_TopSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocation_BottomLeft, CFStringRef) +#define kCVImageBufferChromaLocation_BottomLeft get_CoreVideo_kCVImageBufferChromaLocation_BottomLeftSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocation_Bottom, CFStringRef) +#define kCVImageBufferChromaLocation_Bottom get_CoreVideo_kCVImageBufferChromaLocation_BottomSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferChromaLocation_DV420, CFStringRef) +#define kCVImageBufferChromaLocation_DV420 get_CoreVideo_kCVImageBufferChromaLocation_DV420Singleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferFieldCountKey, CFStringRef) +#define kCVImageBufferFieldCountKey get_CoreVideo_kCVImageBufferFieldCountKeySingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferFieldDetailKey, CFStringRef) +#define kCVImageBufferFieldDetailKey get_CoreVideo_kCVImageBufferFieldDetailKeySingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferFieldDetailTemporalTopFirst, CFStringRef) +#define kCVImageBufferFieldDetailTemporalTopFirst get_CoreVideo_kCVImageBufferFieldDetailTemporalTopFirstSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferFieldDetailTemporalBottomFirst, CFStringRef) +#define kCVImageBufferFieldDetailTemporalBottomFirst get_CoreVideo_kCVImageBufferFieldDetailTemporalBottomFirstSingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferFieldDetailSpatialFirstLineEarly, CFStringRef) +#define kCVImageBufferFieldDetailSpatialFirstLineEarly get_CoreVideo_kCVImageBufferFieldDetailSpatialFirstLineEarlySingleton() +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferFieldDetailSpatialFirstLineLate, CFStringRef) +#define kCVImageBufferFieldDetailSpatialFirstLineLate get_CoreVideo_kCVImageBufferFieldDetailSpatialFirstLineLateSingleton() SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferCGColorSpaceKey, CFStringRef) #define kCVImageBufferCGColorSpaceKey get_CoreVideo_kCVImageBufferCGColorSpaceKeySingleton() SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, CoreVideo, kCVImageBufferCleanApertureKey, CFStringRef) diff --git a/Source/WebCore/platform/graphics/PlatformVideoChromaLocation.h b/Source/WebCore/platform/graphics/PlatformVideoChromaLocation.h new file mode 100644 index 000000000000..bb1ecd5db282 --- /dev/null +++ b/Source/WebCore/platform/graphics/PlatformVideoChromaLocation.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace WebCore { + +// Where a chroma sample sits relative to the luma samples it is shared with. The +// comments give the matching ISO/IEC 23091-2 chroma_sample_loc_type value. +enum class PlatformVideoChromaLocation : uint8_t { + Left, // 0, the MPEG-2 and H.264 default + Center, // 1, as used by MPEG-1 and JPEG + TopLeft, // 2 + Top, // 3 + BottomLeft, // 4 + Bottom, // 5 + // Cr and Cb alternately co-sited with the left luma samples of the same field. + // DV only, no ISO/IEC 23091-2 equivalent. + Dv420, + Unspecified, +}; + +WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, PlatformVideoChromaLocation); + +} // namespace WebCore diff --git a/Source/WebCore/platform/graphics/PlatformVideoColorSpace.cpp b/Source/WebCore/platform/graphics/PlatformVideoColorSpace.cpp index 97b4616fdf38..68a3a7f2fa27 100644 --- a/Source/WebCore/platform/graphics/PlatformVideoColorSpace.cpp +++ b/Source/WebCore/platform/graphics/PlatformVideoColorSpace.cpp @@ -169,6 +169,37 @@ WTF::TextStream& operator<<(WTF::TextStream& ts, PlatformVideoTransferCharacteri return ts; } +WTF::TextStream& operator<<(WTF::TextStream& ts, PlatformVideoChromaLocation chromaLocation) +{ + switch (chromaLocation) { + case PlatformVideoChromaLocation::Left: + ts << "left"_s; + break; + case PlatformVideoChromaLocation::Center: + ts << "center"_s; + break; + case PlatformVideoChromaLocation::TopLeft: + ts << "top-left"_s; + break; + case PlatformVideoChromaLocation::Top: + ts << "top"_s; + break; + case PlatformVideoChromaLocation::BottomLeft: + ts << "bottom-left"_s; + break; + case PlatformVideoChromaLocation::Bottom: + ts << "bottom"_s; + break; + case PlatformVideoChromaLocation::Dv420: + ts << "dv420"_s; + break; + case PlatformVideoChromaLocation::Unspecified: + ts << "unspecified"_s; + break; + } + return ts; +} + WTF::TextStream& operator<<(WTF::TextStream& ts, PlatformVideoColorSpace colorSpace) { ts.dumpProperty("primaries"_s, colorSpace.primaries); @@ -176,6 +207,8 @@ WTF::TextStream& operator<<(WTF::TextStream& ts, PlatformVideoColorSpace colorSp ts.dumpProperty("matrix"_s, colorSpace.matrix); if (colorSpace.fullRange) ts.dumpProperty("full-range"_s, *colorSpace.fullRange); + if (colorSpace.chromaLocation) + ts.dumpProperty("chroma-location"_s, *colorSpace.chromaLocation); return ts; } @@ -191,6 +224,8 @@ void overrideVideoColorSpaceAsNeeded(PlatformVideoColorSpace& colorSpace, const colorSpace.matrix = colorSpaceOverride->matrix; if (colorSpaceOverride->fullRange) colorSpace.fullRange = colorSpaceOverride->fullRange; + if (colorSpaceOverride->chromaLocation) + colorSpace.chromaLocation = colorSpaceOverride->chromaLocation; } } diff --git a/Source/WebCore/platform/graphics/PlatformVideoColorSpace.h b/Source/WebCore/platform/graphics/PlatformVideoColorSpace.h index 47bbdaa795b8..d221eb5315ae 100644 --- a/Source/WebCore/platform/graphics/PlatformVideoColorSpace.h +++ b/Source/WebCore/platform/graphics/PlatformVideoColorSpace.h @@ -25,6 +25,7 @@ #pragma once +#include #include #include #include @@ -39,8 +40,11 @@ struct PlatformVideoColorSpace { std::optional transfer { }; std::optional matrix { }; std::optional fullRange { }; + // Must stay after fullRange: VideoColorSpaceInit aliases this struct, and the + // bindings generator initialises it positionally in VideoColorSpaceInit.idl order. + std::optional chromaLocation { }; - bool isValid() const { return primaries || transfer || matrix || fullRange; } + bool isValid() const { return primaries || transfer || matrix || fullRange || chromaLocation; } friend bool operator==(const PlatformVideoColorSpace&, const PlatformVideoColorSpace&) = default; }; diff --git a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp index 2048c1bcae4a..5c7c324107bf 100644 --- a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp +++ b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp @@ -165,9 +165,69 @@ std::optional colorSpaceFromFormatDescription(CMFormatD if (RetainPtr fullRange = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(formatDescription, PAL::kCMFormatDescriptionExtension_FullRangeVideo))) colorSpace.fullRange = CFBooleanGetValue(fullRange.get()); + // Progressive content carries the same location in both fields. There is no default to fall + // back on: an absent key means the decoder picks its own siting. + RetainPtr chromaLocation = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(formatDescription, kCVImageBufferChromaLocationTopFieldKey)); + if (!chromaLocation) + chromaLocation = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(formatDescription, kCVImageBufferChromaLocationBottomFieldKey)); + + if (chromaLocation) { + if (safeCFEqual(chromaLocation.get(), kCVImageBufferChromaLocation_Left)) + colorSpace.chromaLocation = PlatformVideoChromaLocation::Left; + else if (safeCFEqual(chromaLocation.get(), kCVImageBufferChromaLocation_Center)) + colorSpace.chromaLocation = PlatformVideoChromaLocation::Center; + else if (safeCFEqual(chromaLocation.get(), kCVImageBufferChromaLocation_TopLeft)) + colorSpace.chromaLocation = PlatformVideoChromaLocation::TopLeft; + else if (safeCFEqual(chromaLocation.get(), kCVImageBufferChromaLocation_Top)) + colorSpace.chromaLocation = PlatformVideoChromaLocation::Top; + else if (safeCFEqual(chromaLocation.get(), kCVImageBufferChromaLocation_BottomLeft)) + colorSpace.chromaLocation = PlatformVideoChromaLocation::BottomLeft; + else if (safeCFEqual(chromaLocation.get(), kCVImageBufferChromaLocation_Bottom)) + colorSpace.chromaLocation = PlatformVideoChromaLocation::Bottom; + else if (safeCFEqual(chromaLocation.get(), kCVImageBufferChromaLocation_DV420)) + colorSpace.chromaLocation = PlatformVideoChromaLocation::Dv420; + } + return colorSpace; } +std::optional fieldCountFromFormatDescription(CMFormatDescriptionRef formatDescription) +{ + if (!formatDescription) + return { }; + + RetainPtr fieldCount = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(formatDescription, kCVImageBufferFieldCountKey)); + if (!fieldCount) + return { }; + + int value = 0; + if (!CFNumberGetValue(fieldCount.get(), kCFNumberIntType, &value) || value < 1 || value > 2) + return { }; + + return static_cast(value); +} + +std::optional fieldDetailFromFormatDescription(CMFormatDescriptionRef formatDescription) +{ + if (!formatDescription) + return { }; + + RetainPtr fieldDetail = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(formatDescription, kCVImageBufferFieldDetailKey)); + if (!fieldDetail) + return { }; + + if (safeCFEqual(fieldDetail.get(), kCVImageBufferFieldDetailTemporalTopFirst)) + return PlatformVideoFieldDetail::TemporalTopFirst; + if (safeCFEqual(fieldDetail.get(), kCVImageBufferFieldDetailTemporalBottomFirst)) + return PlatformVideoFieldDetail::TemporalBottomFirst; + if (safeCFEqual(fieldDetail.get(), kCVImageBufferFieldDetailSpatialFirstLineEarly)) + return PlatformVideoFieldDetail::SpatialFirstLineEarly; + if (safeCFEqual(fieldDetail.get(), kCVImageBufferFieldDetailSpatialFirstLineLate)) + return PlatformVideoFieldDetail::SpatialFirstLineLate; + + return { }; +} + String codecFromFormatDescription(CMFormatDescriptionRef formatDescription) { if (!formatDescription) diff --git a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h index 13ae0c6d0a82..81420a6db492 100644 --- a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h +++ b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h @@ -40,6 +40,8 @@ struct PlatformVideoColorSpace; TrackInfoTrackType typeFromFormatDescription(CMFormatDescriptionRef); FloatSize presentationSizeFromFormatDescription(CMFormatDescriptionRef); WEBCORE_EXPORT std::optional colorSpaceFromFormatDescription(CMFormatDescriptionRef); +WEBCORE_EXPORT std::optional fieldCountFromFormatDescription(CMFormatDescriptionRef); +WEBCORE_EXPORT std::optional fieldDetailFromFormatDescription(CMFormatDescriptionRef); String codecFromFormatDescription(CMFormatDescriptionRef); bool formatDescriptionIsProtected(CMFormatDescriptionRef); WEBCORE_EXPORT std::optional immersiveVideoMetadataFromFormatDescription(CMFormatDescriptionRef); diff --git a/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm b/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm index a0882392e0e6..651baf43476d 100644 --- a/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm +++ b/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm @@ -39,6 +39,8 @@ #import "SharedBuffer.h" #import "WebMAudioUtilitiesCocoa.h" #import +#import +#import #import #import #import @@ -239,11 +241,15 @@ static FourCC cfStringToFourCC(CFStringRef string) #endif } + // A sizing hint only; exceeding it costs a rehash. The video counts include the keys + // createFormatDescriptionFromTrackInfo() goes on to add: FullRangeVideo, BitsPerComponent, + // ColorPrimaries, TransferFunction, GammaLevel, YCbCrMatrix, both ChromaLocation fields, + // FieldCount, FieldDetail and PixelAspectRatio. size_t maxNumberOfElements = [&] { #if ENABLE(ENCRYPTED_MEDIA) && HAVE(AVCONTENTKEYSESSION) - return info.isAudio() ? 5 : 9; + return info.isAudio() ? 5 : 15; #else - return 5; + return info.isAudio() ? 5 : 12; #endif }(); RetainPtr extensions = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, maxNumberOfElements, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); @@ -378,6 +384,44 @@ static CFStringRef convertToCMYCbCRMatrix(PlatformVideoMatrixCoefficients coeffi } } +static CFStringRef convertToCMChromaLocation(PlatformVideoChromaLocation chromaLocation) +{ + switch (chromaLocation) { + case PlatformVideoChromaLocation::Left: + return kCVImageBufferChromaLocation_Left; + case PlatformVideoChromaLocation::Center: + return kCVImageBufferChromaLocation_Center; + case PlatformVideoChromaLocation::TopLeft: + return kCVImageBufferChromaLocation_TopLeft; + case PlatformVideoChromaLocation::Top: + return kCVImageBufferChromaLocation_Top; + case PlatformVideoChromaLocation::BottomLeft: + return kCVImageBufferChromaLocation_BottomLeft; + case PlatformVideoChromaLocation::Bottom: + return kCVImageBufferChromaLocation_Bottom; + case PlatformVideoChromaLocation::Dv420: + return kCVImageBufferChromaLocation_DV420; + default: + return nullptr; + } +} + +static CFStringRef convertToCMFieldDetail(PlatformVideoFieldDetail fieldDetail) +{ + switch (fieldDetail) { + case PlatformVideoFieldDetail::TemporalTopFirst: + return kCVImageBufferFieldDetailTemporalTopFirst; + case PlatformVideoFieldDetail::TemporalBottomFirst: + return kCVImageBufferFieldDetailTemporalBottomFirst; + case PlatformVideoFieldDetail::SpatialFirstLineEarly: + return kCVImageBufferFieldDetailSpatialFirstLineEarly; + case PlatformVideoFieldDetail::SpatialFirstLineLate: + return kCVImageBufferFieldDetailSpatialFirstLineLate; + } + ASSERT_NOT_REACHED(); + return nullptr; +} + RetainPtr createFormatDescriptionFromTrackInfo(const TrackInfo& info) { ASSERT(info.isVideo() || info.isAudio()); @@ -439,13 +483,37 @@ static CFStringRef convertToCMYCbCRMatrix(PlatformVideoMatrixCoefficients coeffi if (RetainPtr cmMatrix = convertToCMYCbCRMatrix(*videoInfo.colorSpace().matrix)) CFDictionaryAddValue(extensions.get(), kCVImageBufferYCbCrMatrixKey, cmMatrix.get()); } - if (videoInfo.size() != videoInfo.displaySize()) { - double horizontalRatio = videoInfo.displaySize().width() / videoInfo.size().width(); - double verticalRatio = videoInfo.displaySize().height() / videoInfo.size().height(); - CFDictionaryAddValue(extensions.get(), kCVImageBufferPixelAspectRatioKey, @{ - (__bridge NSString*)kCVImageBufferPixelAspectRatioHorizontalSpacingKey : @(horizontalRatio), - (__bridge NSString*)kCVImageBufferPixelAspectRatioVerticalSpacingKey : @(verticalRatio) - }); + + if (videoInfo.colorSpace().chromaLocation) { + if (RetainPtr cmChromaLocation = convertToCMChromaLocation(*videoInfo.colorSpace().chromaLocation)) { + CFDictionaryAddValue(extensions.get(), kCVImageBufferChromaLocationTopFieldKey, cmChromaLocation.get()); + CFDictionaryAddValue(extensions.get(), kCVImageBufferChromaLocationBottomFieldKey, cmChromaLocation.get()); + } + } + + if (videoInfo.fieldCount()) + CFDictionaryAddValue(extensions.get(), kCVImageBufferFieldCountKey, (__bridge CFTypeRef)@(*videoInfo.fieldCount())); + + if (videoInfo.fieldDetail()) { + if (RetainPtr cmFieldDetail = convertToCMFieldDetail(*videoInfo.fieldDetail())) + CFDictionaryAddValue(extensions.get(), kCVImageBufferFieldDetailKey, cmFieldDetail.get()); + } + + if (!videoInfo.size().isEmpty() && !videoInfo.displaySize().isEmpty() && videoInfo.size() != videoInfo.displaySize()) { + // The two spacings are the numerator and denominator of a single fraction, as stored in + // the `pasp` box. Emit them as an exact integer pair rather than as the two quotients + // displaySize / size, which only approximate the ratio. + auto horizontalSpacing = std::llround(videoInfo.displaySize().width() * videoInfo.size().height()); + auto verticalSpacing = std::llround(videoInfo.displaySize().height() * videoInfo.size().width()); + if (horizontalSpacing > 0 && verticalSpacing > 0) { + auto divisor = std::gcd(horizontalSpacing, verticalSpacing); + horizontalSpacing /= divisor; + verticalSpacing /= divisor; + CFDictionaryAddValue(extensions.get(), kCVImageBufferPixelAspectRatioKey, @{ + (__bridge NSString*)kCVImageBufferPixelAspectRatioHorizontalSpacingKey : @(horizontalSpacing), + (__bridge NSString*)kCVImageBufferPixelAspectRatioVerticalSpacingKey : @(verticalSpacing) + }); + } } #if PLATFORM(VISION) @@ -534,6 +602,8 @@ static CFStringRef convertToCMYCbCRMatrix(PlatformVideoMatrixCoefficients coeffi .displaySize = presentationSizeFromFormatDescription(description), .bitDepth = static_cast(bitDepth), .colorSpace = colorSpaceFromFormatDescription(description).value_or(PlatformVideoColorSpace { }), + .fieldCount = fieldCountFromFormatDescription(description), + .fieldDetail = fieldDetailFromFormatDescription(description), .extensionAtoms = WTF::move(extensionAtoms), #if PLATFORM(VISION) .immersiveVideoMetadata = immersiveVideoMetadataFromFormatDescription(description) @@ -733,6 +803,12 @@ void attachColorSpaceToPixelBuffer(const PlatformVideoColorSpace& colorSpace, CV } if (colorSpace.matrix) CVBufferSetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, convertToCMYCbCRMatrix(*colorSpace.matrix), kCVAttachmentMode_ShouldPropagate); + if (colorSpace.chromaLocation) { + if (RetainPtr cmChromaLocation = convertToCMChromaLocation(*colorSpace.chromaLocation)) { + CVBufferSetAttachment(pixelBuffer, kCVImageBufferChromaLocationTopFieldKey, cmChromaLocation.get(), kCVAttachmentMode_ShouldPropagate); + CVBufferSetAttachment(pixelBuffer, kCVImageBufferChromaLocationBottomFieldKey, cmChromaLocation.get(), kCVAttachmentMode_ShouldPropagate); + } + } } PlatformVideoColorSpace computeVideoFrameColorSpace(CVPixelBufferRef pixelBuffer) @@ -799,7 +875,26 @@ PlatformVideoColorSpace computeVideoFrameColorSpace(CVPixelBufferRef pixelBuffer // FIXME: We should do a more comprehensive check. bool isFullRange = pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange && pixelFormat != kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange; - return { primaries, transfer, matrix, isFullRange }; + std::optional chromaLocation; + RetainPtr pixelChromaLocation = CVBufferGetAttachment(pixelBuffer, kCVImageBufferChromaLocationTopFieldKey, nil); + if (!pixelChromaLocation) + pixelChromaLocation = CVBufferGetAttachment(pixelBuffer, kCVImageBufferChromaLocationBottomFieldKey, nil); + if (safeCFEqual(pixelChromaLocation.get(), kCVImageBufferChromaLocation_Left)) + chromaLocation = PlatformVideoChromaLocation::Left; + else if (safeCFEqual(pixelChromaLocation.get(), kCVImageBufferChromaLocation_Center)) + chromaLocation = PlatformVideoChromaLocation::Center; + else if (safeCFEqual(pixelChromaLocation.get(), kCVImageBufferChromaLocation_TopLeft)) + chromaLocation = PlatformVideoChromaLocation::TopLeft; + else if (safeCFEqual(pixelChromaLocation.get(), kCVImageBufferChromaLocation_Top)) + chromaLocation = PlatformVideoChromaLocation::Top; + else if (safeCFEqual(pixelChromaLocation.get(), kCVImageBufferChromaLocation_BottomLeft)) + chromaLocation = PlatformVideoChromaLocation::BottomLeft; + else if (safeCFEqual(pixelChromaLocation.get(), kCVImageBufferChromaLocation_Bottom)) + chromaLocation = PlatformVideoChromaLocation::Bottom; + else if (safeCFEqual(pixelChromaLocation.get(), kCVImageBufferChromaLocation_DV420)) + chromaLocation = PlatformVideoChromaLocation::Dv420; + + return { .primaries = primaries, .transfer = transfer, .matrix = matrix, .fullRange = isFullRange, .chromaLocation = chromaLocation }; } PacketDurationParser::PacketDurationParser(const AudioInfo& info) diff --git a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp index ef146821c19c..42ab446911dc 100644 --- a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp +++ b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp @@ -281,7 +281,7 @@ Ref LibWebRTCVPXInternalVideoEncoder::encode(VideoE if (auto pixelFormat = convertVideoFramePixelFormat(protectedFrame->pixelFormat(), true)) { if (isRGBVideoPixelFormat(*pixelFormat)) { // We do our own conversion to get matching color space handling, instead of letting libwebrtc do it. - colorSpace = { PlatformVideoColorPrimaries::Bt709, PlatformVideoTransferCharacteristics::Bt709, PlatformVideoMatrixCoefficients::Bt709, false }; + colorSpace = { .primaries = PlatformVideoColorPrimaries::Bt709, .transfer = PlatformVideoTransferCharacteristics::Bt709, .matrix = PlatformVideoMatrixCoefficients::Bt709, .fullRange = false }; buffer = ImageTransferSessionVT::convertPixelBuffer(buffer.get(), kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, ImageTransferSessionVT::DestinationColorSpace::BT709); } } @@ -343,7 +343,7 @@ webrtc::EncodedImageCallback::Result LibWebRTCVPXInternalVideoEncoder::OnEncoded if (m_shouldCallDescriptionCallback) { m_shouldCallDescriptionCallback = false; VideoEncoder::ActiveConfiguration configuration; - configuration.colorSpace = m_currentColorSpace.value_or(PlatformVideoColorSpace { PlatformVideoColorPrimaries::Bt709, PlatformVideoTransferCharacteristics::Bt709, PlatformVideoMatrixCoefficients::Bt709, false }); + configuration.colorSpace = m_currentColorSpace.value_or(PlatformVideoColorSpace { .primaries = PlatformVideoColorPrimaries::Bt709, .transfer = PlatformVideoTransferCharacteristics::Bt709, .matrix = PlatformVideoMatrixCoefficients::Bt709, .fullRange = false }); m_descriptionCallback(WTF::move(configuration)); } m_outputCallback({ WTF::move(encodedFrame) }); diff --git a/Source/WebCore/platform/mediastream/cocoa/RealtimeOutgoingVideoSourceCocoa.cpp b/Source/WebCore/platform/mediastream/cocoa/RealtimeOutgoingVideoSourceCocoa.cpp index 8d129e096bf7..86e6760dfff2 100644 --- a/Source/WebCore/platform/mediastream/cocoa/RealtimeOutgoingVideoSourceCocoa.cpp +++ b/Source/WebCore/platform/mediastream/cocoa/RealtimeOutgoingVideoSourceCocoa.cpp @@ -90,7 +90,7 @@ void RealtimeOutgoingVideoSourceCocoa::videoFrameAvailable(VideoFrame& videoFram auto colorSpace = [&] { if (auto pixelFormat = convertVideoFramePixelFormat(videoFrame.pixelFormat(), true)) { if (isRGBVideoPixelFormat(*pixelFormat)) - return PlatformVideoColorSpace { PlatformVideoColorPrimaries::Bt709, PlatformVideoTransferCharacteristics::Bt709, PlatformVideoMatrixCoefficients::Bt709, false }; + return PlatformVideoColorSpace { .primaries = PlatformVideoColorPrimaries::Bt709, .transfer = PlatformVideoTransferCharacteristics::Bt709, .matrix = PlatformVideoMatrixCoefficients::Bt709, .fullRange = false }; } return videoFrame.colorSpace(); }(); diff --git a/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCVideoFrameUtilities.cpp b/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCVideoFrameUtilities.cpp index 15ed4f2ed791..1f8a6f0f11c3 100644 --- a/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCVideoFrameUtilities.cpp +++ b/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCVideoFrameUtilities.cpp @@ -183,7 +183,7 @@ std::optional colorSpaceFromLibWebRTCColorSpace(const w break; }; - return PlatformVideoColorSpace { primaries, transfer, matrix, fullRange }; + return PlatformVideoColorSpace { .primaries = primaries, .transfer = transfer, .matrix = matrix, .fullRange = fullRange }; } std::optional colorSpaceFromLibWebRTCVideoFrame(const webrtc::VideoFrame& frame) diff --git a/Source/WebCore/platform/mediastream/libwebrtc/VideoFrameLibWebRTC.cpp b/Source/WebCore/platform/mediastream/libwebrtc/VideoFrameLibWebRTC.cpp index efe6b118163e..bd2c03bf6b79 100644 --- a/Source/WebCore/platform/mediastream/libwebrtc/VideoFrameLibWebRTC.cpp +++ b/Source/WebCore/platform/mediastream/libwebrtc/VideoFrameLibWebRTC.cpp @@ -36,7 +36,7 @@ namespace WebCore { static PlatformVideoColorSpace NODELETE defaultVPXColorSpace() { - return { PlatformVideoColorPrimaries::Bt709, PlatformVideoTransferCharacteristics::Bt709, PlatformVideoMatrixCoefficients::Bt709, false }; + return { .primaries = PlatformVideoColorPrimaries::Bt709, .transfer = PlatformVideoTransferCharacteristics::Bt709, .matrix = PlatformVideoMatrixCoefficients::Bt709, .fullRange = false }; } RefPtr VideoFrameLibWebRTC::create(MediaTime presentationTime, bool isMirrored, Rotation rotation, std::optional&& colorSpace, Ref&& buffer, ConversionCallback&& conversionCallback) diff --git a/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/RealtimeOutgoingVideoSourceLibWebRTC.cpp b/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/RealtimeOutgoingVideoSourceLibWebRTC.cpp index 864786c5ffa4..369906116af2 100644 --- a/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/RealtimeOutgoingVideoSourceLibWebRTC.cpp +++ b/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/RealtimeOutgoingVideoSourceLibWebRTC.cpp @@ -77,7 +77,7 @@ void RealtimeOutgoingVideoSourceLibWebRTC::videoFrameAvailable(VideoFrame& video auto colorSpace = [&] { if (auto pixelFormat = convertVideoFramePixelFormat(videoFrame.pixelFormat(), true)) { if (isRGBVideoPixelFormat(*pixelFormat)) - return PlatformVideoColorSpace { PlatformVideoColorPrimaries::Bt709, PlatformVideoTransferCharacteristics::Bt709, PlatformVideoMatrixCoefficients::Bt709, false }; + return PlatformVideoColorSpace { .primaries = PlatformVideoColorPrimaries::Bt709, .transfer = PlatformVideoTransferCharacteristics::Bt709, .matrix = PlatformVideoMatrixCoefficients::Bt709, .fullRange = false }; } return videoFrame.colorSpace(); }(); diff --git a/Source/WebKit/GPUProcess/webrtc/LibWebRTCCodecsProxy.mm b/Source/WebKit/GPUProcess/webrtc/LibWebRTCCodecsProxy.mm index 7c339de446b2..037e90a914c0 100644 --- a/Source/WebKit/GPUProcess/webrtc/LibWebRTCCodecsProxy.mm +++ b/Source/WebKit/GPUProcess/webrtc/LibWebRTCCodecsProxy.mm @@ -368,7 +368,7 @@ static bool validateEncoderConfiguration(WebCore::VideoCodecType codecType, cons }); auto newConfigurationBlock = makeBlockPtr([connection = m_connection, identifier](const uint8_t* buffer, size_t size) { // Current encoders are limited to this configuration. We might want in the future to let encoders notify which colorSpace they are selecting. - PlatformVideoColorSpace colorSpace { PlatformVideoColorPrimaries::Bt709, PlatformVideoTransferCharacteristics::Iec6196621, PlatformVideoMatrixCoefficients::Bt709, true }; + PlatformVideoColorSpace colorSpace { .primaries = PlatformVideoColorPrimaries::Bt709, .transfer = PlatformVideoTransferCharacteristics::Iec6196621, .matrix = PlatformVideoMatrixCoefficients::Bt709, .fullRange = true }; connection->send(Messages::LibWebRTCCodecs::SetEncodingConfiguration { identifier, unsafeMakeSpan(buffer, size), colorSpace }, 0); }); diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in index 7114f8403377..56d13d99cb61 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in @@ -3873,11 +3873,23 @@ enum class WebCore::PlatformVideoMatrixCoefficients : uint8_t { Unspecified, }; +enum class WebCore::PlatformVideoChromaLocation : uint8_t { + Left, + Center, + TopLeft, + Top, + BottomLeft, + Bottom, + Dv420, + Unspecified, +}; + [AdditionalEncoder=StreamConnectionEncoder] struct WebCore::PlatformVideoColorSpace { std::optional primaries; std::optional transfer; std::optional matrix; std::optional fullRange; + std::optional chromaLocation; }; #if ENABLE(CONTENT_EXTENSIONS) @@ -6933,6 +6945,14 @@ enum class WebCore::EncryptionBoxType : uint8_t { TransportStreamEncryptionInitData }; +header: +enum class WebCore::PlatformVideoFieldDetail : uint8_t { + TemporalTopFirst, + TemporalBottomFirst, + SpatialFirstLineEarly, + SpatialFirstLineLate +}; + using WebCore::TrackInfoAtomData = std::pair>; header: @@ -6968,6 +6988,8 @@ header: WebCore::FloatSize displaySize; uint8_t bitDepth; WebCore::PlatformVideoColorSpace colorSpace; + std::optional fieldCount; + std::optional fieldDetail; Vector>> extensionAtoms; #if PLATFORM(VISION) diff --git a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm index 5efab2daa408..68a13d083d8d 100644 --- a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm +++ b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm @@ -28,9 +28,11 @@ #if USE(AVFOUNDATION) #import +#import #import #import #import +#import #import #import #import @@ -655,6 +657,208 @@ EXPECT_EQ(span1[2], 0x05); } +// ---- Chroma Location ---- + +TEST(CMUtilities, ChromaLocationRoundTrip) +{ + using C = WebCore::PlatformVideoChromaLocation; + + // The exhaustive switch (no default) ensures the compiler warns when new + // PlatformVideoChromaLocation values are added without updating this test. + // std::nullopt means the value has no CoreVideo mapping and is not tested. + auto testChromaLocation = [&](C input) { + std::optional expected; + switch (input) { + case C::Left: expected = C::Left; break; + case C::Center: expected = C::Center; break; + case C::TopLeft: expected = C::TopLeft; break; + case C::Top: expected = C::Top; break; + case C::BottomLeft: expected = C::BottomLeft; break; + case C::Bottom: expected = C::Bottom; break; + case C::Dv420: expected = C::Dv420; break; + case C::Unspecified: /* no CV mapping */ break; + } + if (!expected) + return; + auto cs = baselineColorSpace(); + cs.chromaLocation = input; + auto desc = makeColorSpaceFormatDescription(cs); + ASSERT_TRUE(desc) << "createFormatDescriptionFromTrackInfo returned null for chroma location " << (int)input; + auto result = WebCore::colorSpaceFromFormatDescription(desc.get()); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->chromaLocation, expected); + }; + + testChromaLocation(C::Left); + testChromaLocation(C::Center); + testChromaLocation(C::TopLeft); + testChromaLocation(C::Top); + testChromaLocation(C::BottomLeft); + testChromaLocation(C::Bottom); + testChromaLocation(C::Dv420); + testChromaLocation(C::Unspecified); +} + +// A CMFormatDescription with no chroma location extension must not be given one, +// so that the decoder keeps applying its own default siting. +TEST(CMUtilities, AbsentChromaLocationStaysAbsent) +{ + NSDictionary *extensions = @{ + (__bridge NSString *)kCVImageBufferColorPrimariesKey: (__bridge NSString *)kCVImageBufferColorPrimaries_ITU_R_709_2, + (__bridge NSString *)kCVImageBufferTransferFunctionKey: (__bridge NSString *)kCVImageBufferTransferFunction_ITU_R_709_2, + (__bridge NSString *)kCVImageBufferYCbCrMatrixKey: (__bridge NSString *)kCVImageBufferYCbCrMatrix_ITU_R_709_2, + }; + CMFormatDescriptionRef rawDesc = nullptr; + PAL::CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_H264, 640, 480, (__bridge CFDictionaryRef)extensions, &rawDesc); + RetainPtr desc = adoptCF(rawDesc); + ASSERT_TRUE(desc); + + auto colorSpace = WebCore::colorSpaceFromFormatDescription(desc.get()); + ASSERT_TRUE(colorSpace.has_value()); + EXPECT_FALSE(colorSpace->chromaLocation.has_value()); + + RefPtr videoInfo = WebCore::createVideoInfoFromFormatDescription(desc.get()); + ASSERT_TRUE(videoInfo); + RetainPtr reconstructed = WebCore::createFormatDescriptionFromTrackInfo(*videoInfo); + ASSERT_TRUE(reconstructed); + RetainPtr topField = PAL::CMFormatDescriptionGetExtension(reconstructed.get(), kCVImageBufferChromaLocationTopFieldKey); + EXPECT_FALSE(topField); + RetainPtr bottomField = PAL::CMFormatDescriptionGetExtension(reconstructed.get(), kCVImageBufferChromaLocationBottomFieldKey); + EXPECT_FALSE(bottomField); +} + +// Verifies the WebContent -> GPU-process round trip preserves a non-default chroma +// siting. H.264 content signalling chroma_sample_loc_type 1 (Center) reaches the GPU +// process as a rebuilt CMFormatDescription; without the chroma location extension +// VideoToolbox would fall back to Left siting. +TEST(CMUtilities, ChromaLocationSurvivesFormatDescriptionRoundTrip) +{ + NSDictionary *extensions = @{ + (__bridge NSString *)kCVImageBufferChromaLocationTopFieldKey: (__bridge NSString *)kCVImageBufferChromaLocation_Center, + (__bridge NSString *)kCVImageBufferChromaLocationBottomFieldKey: (__bridge NSString *)kCVImageBufferChromaLocation_Center, + (__bridge NSString *)kCVImageBufferColorPrimariesKey: (__bridge NSString *)kCVImageBufferColorPrimaries_SMPTE_C, + (__bridge NSString *)kCVImageBufferTransferFunctionKey: (__bridge NSString *)kCVImageBufferTransferFunction_ITU_R_709_2, + (__bridge NSString *)kCVImageBufferYCbCrMatrixKey: (__bridge NSString *)kCVImageBufferYCbCrMatrix_ITU_R_601_4, + }; + CMFormatDescriptionRef rawDesc = nullptr; + PAL::CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_H264, 480, 360, (__bridge CFDictionaryRef)extensions, &rawDesc); + RetainPtr desc = adoptCF(rawDesc); + ASSERT_TRUE(desc); + + RefPtr videoInfo = WebCore::createVideoInfoFromFormatDescription(desc.get()); + ASSERT_TRUE(videoInfo); + EXPECT_EQ(videoInfo->colorSpace().chromaLocation, WebCore::PlatformVideoChromaLocation::Center); + + RetainPtr reconstructed = WebCore::createFormatDescriptionFromTrackInfo(*videoInfo); + ASSERT_TRUE(reconstructed); + + RetainPtr topField = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(reconstructed.get(), kCVImageBufferChromaLocationTopFieldKey)); + ASSERT_TRUE(topField); + EXPECT_TRUE(CFEqual(topField.get(), kCVImageBufferChromaLocation_Center)); + + RetainPtr bottomField = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(reconstructed.get(), kCVImageBufferChromaLocationBottomFieldKey)); + ASSERT_TRUE(bottomField); + EXPECT_TRUE(CFEqual(bottomField.get(), kCVImageBufferChromaLocation_Center)); +} + +// ---- Field Count and Field Detail ---- + +TEST(CMUtilities, FieldCountAndDetailRoundTrip) +{ + using F = WebCore::PlatformVideoFieldDetail; + + auto testFieldDetail = [&](uint8_t fieldCount, F input) { + auto videoInfo = WebCore::VideoInfo::create({ + { .codecName = WebCore::FourCC('avc1') }, { + .size = { 640, 480 }, + .displaySize = { 640, 480 }, + .fieldCount = fieldCount, + .fieldDetail = input, + } + }); + RetainPtr desc = WebCore::createFormatDescriptionFromTrackInfo(videoInfo); + ASSERT_TRUE(desc) << "createFormatDescriptionFromTrackInfo returned null for field detail " << (int)input; + EXPECT_EQ(WebCore::fieldCountFromFormatDescription(desc.get()), fieldCount); + EXPECT_EQ(WebCore::fieldDetailFromFormatDescription(desc.get()), input); + }; + + testFieldDetail(2, F::TemporalTopFirst); + testFieldDetail(2, F::TemporalBottomFirst); + testFieldDetail(2, F::SpatialFirstLineEarly); + testFieldDetail(2, F::SpatialFirstLineLate); + testFieldDetail(1, F::TemporalTopFirst); +} + +TEST(CMUtilities, AbsentFieldInfoStaysAbsent) +{ + auto videoInfo = WebCore::VideoInfo::create({ + { .codecName = WebCore::FourCC('avc1') }, { + .size = { 640, 480 }, + .displaySize = { 640, 480 }, + } + }); + RetainPtr desc = WebCore::createFormatDescriptionFromTrackInfo(videoInfo); + ASSERT_TRUE(desc); + EXPECT_FALSE(WebCore::fieldCountFromFormatDescription(desc.get()).has_value()); + EXPECT_FALSE(WebCore::fieldDetailFromFormatDescription(desc.get()).has_value()); +} + +// ---- Pixel Aspect Ratio ---- + +// The two pixel-aspect-ratio spacings form a single fraction, as stored in the `pasp` box. +// A non-square ratio must be emitted as an exact reduced integer pair rather than as the two +// fractional quotients displaySize / size. +TEST(CMUtilities, PixelAspectRatioUsesIntegerSpacings) +{ + // 720x480 displayed at 16:9 gives a 32:27 pixel aspect ratio. + auto videoInfo = WebCore::VideoInfo::create({ + { .codecName = WebCore::FourCC('avc1') }, { + .size = { 720, 480 }, + .displaySize = { 853.3333333, 480 }, + } + }); + RetainPtr desc = WebCore::createFormatDescriptionFromTrackInfo(videoInfo); + ASSERT_TRUE(desc); + + RetainPtr pixelAspectRatio = dynamic_cf_cast(PAL::CMFormatDescriptionGetExtension(desc.get(), kCVImageBufferPixelAspectRatioKey)); + ASSERT_TRUE(pixelAspectRatio); + + RetainPtr horizontal = dynamic_cf_cast(CFDictionaryGetValue(pixelAspectRatio.get(), kCVImageBufferPixelAspectRatioHorizontalSpacingKey)); + ASSERT_TRUE(horizontal); + EXPECT_FALSE(CFNumberIsFloatType(horizontal.get())); + + RetainPtr vertical = dynamic_cf_cast(CFDictionaryGetValue(pixelAspectRatio.get(), kCVImageBufferPixelAspectRatioVerticalSpacingKey)); + ASSERT_TRUE(vertical); + EXPECT_FALSE(CFNumberIsFloatType(vertical.get())); + + int64_t horizontalSpacing = 0; + int64_t verticalSpacing = 0; + ASSERT_TRUE(CFNumberGetValue(horizontal.get(), kCFNumberSInt64Type, &horizontalSpacing)); + ASSERT_TRUE(CFNumberGetValue(vertical.get(), kCFNumberSInt64Type, &verticalSpacing)); + EXPECT_EQ(horizontalSpacing, 32); + EXPECT_EQ(verticalSpacing, 27); + + // The emitted ratio must still make the presentation dimensions match displaySize. + auto presentationSize = PAL::CMVideoFormatDescriptionGetPresentationDimensions(desc.get(), true, true); + EXPECT_NEAR(presentationSize.width, videoInfo->displaySize().width(), 1); + EXPECT_EQ(presentationSize.height, videoInfo->displaySize().height()); +} + +// A square pixel aspect ratio carries no extension, matching CoreVideo's default. +TEST(CMUtilities, SquarePixelAspectRatioEmitsNoExtension) +{ + auto videoInfo = WebCore::VideoInfo::create({ + { .codecName = WebCore::FourCC('avc1') }, { + .size = { 480, 360 }, + .displaySize = { 480, 360 }, + } + }); + RetainPtr desc = WebCore::createFormatDescriptionFromTrackInfo(videoInfo); + ASSERT_TRUE(desc); + RetainPtr pixelAspectRatio = PAL::CMFormatDescriptionGetExtension(desc.get(), kCVImageBufferPixelAspectRatioKey); + EXPECT_FALSE(pixelAspectRatio); +} + } // namespace TestWebKitAPI #endif // USE(AVFOUNDATION) From e8032440e4aed34eeb637b153757c03f9ecdeac1 Mon Sep 17 00:00:00 2001 From: Dana Estra Date: Fri, 28 Aug 2026 09:08:24 -0700 Subject: [PATCH 043/103] =?UTF-8?q?ESPN.com=20on=20iPhone:=20After=20exiti?= =?UTF-8?q?ng=20full=20screen,=20video=20pauses=20and=20tapping=20the=20re?= =?UTF-8?q?sume=20button=20doesn=E2=80=99t=20do=20anything=20on=20espn.com?= =?UTF-8?q?=20https://bugs.webkit.org/show=5Fbug.cgi=3Fid=3D322670=20rdar:?= =?UTF-8?q?//184169028?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed by Jer Noble. When exiting video fullscreen on espn.com on iPhone, the video gets stuck in a paused state. The quirk NeedsPauseBeforeFullscreenExit was added to fix this however it no longer works with the current configuration of the website. This patch removes the old quirk and adds a new one NeedsSuppressedPauseEventOnFullscreenExit. The quirk prevents the pause event from being dispatched while fullscreen is being exited. This allows the site's internal state machine to remain in allignment with the real state of the video. No new tests. * Source/WebCore/html/HTMLMediaElement.cpp: (WebCore::HTMLMediaElement::pause): (WebCore::HTMLMediaElement::pauseInternal): (WebCore::HTMLMediaElement::exitFullscreen): * Source/WebCore/html/HTMLMediaElement.h: * Source/WebCore/page/Quirks.cpp: * Source/WebCore/page/Quirks.h: * Source/WebCore/page/QuirksData.h: Canonical link: https://commits.webkit.org/320051@main --- Source/WebCore/html/HTMLMediaElement.cpp | 16 +++++++++------- Source/WebCore/html/HTMLMediaElement.h | 2 +- Source/WebCore/page/QuirkNames.h | 2 +- Source/WebCore/page/QuirkTable.cpp | 4 ++-- Source/WebCore/page/Quirks.cpp | 6 +++--- Source/WebCore/page/Quirks.h | 2 +- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp index d4f630ee594d..1320a44d8315 100644 --- a/Source/WebCore/html/HTMLMediaElement.cpp +++ b/Source/WebCore/html/HTMLMediaElement.cpp @@ -4769,12 +4769,14 @@ void HTMLMediaElement::pause() if (processingUserGestureForMedia()) removeBehaviorRestrictionsAfterFirstUserGesture(MediaElementSession::RequireUserGestureToControlControlsManager); - pauseInternal(); + bool suppressPauseEvent = m_videoFullscreenMode == VideoFullscreenModeStandard && protect(document())->quirks().needsSuppressedPauseEventOnFullscreenExitQuirk(); + + pauseInternal(!suppressPauseEvent); // If we have a pending seek, ensure playback doesn't resume. m_wasPlayingBeforeSeeking = false; } -void HTMLMediaElement::pauseInternal() +void HTMLMediaElement::pauseInternal(bool dispatchPauseEvent) { HTMLMEDIAELEMENT_RELEASE_LOG(PauseInternal); @@ -4819,7 +4821,8 @@ void HTMLMediaElement::pauseInternal() if (!m_paused && !m_pausedInternal) { setPaused(true); scheduleTimeupdateEvent(false); - scheduleEvent(eventNames().pauseEvent); + if (dispatchPauseEvent) + scheduleEvent(eventNames().pauseEvent); if (!hadInFlightPlayRequest || !m_playPromiseSettlementGuaranteed) scheduleRejectPendingPlayPromises(DOMException::create(ExceptionCode::AbortError)); if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) @@ -7997,9 +8000,11 @@ void HTMLMediaElement::exitFullscreen() if (!videoElement) return; + bool suppressPauseEvent = protect(document())->quirks().needsSuppressedPauseEventOnFullscreenExitQuirk(); + if (!paused() && protect(mediaSession())->requiresFullscreenForVideoPlayback()) { if (!document().settings().allowsInlineMediaPlaybackAfterFullscreen() || isVideoTooSmallForInlinePlayback()) - pauseInternal(); + pauseInternal(!suppressPauseEvent); else { // Allow inline playback, but set a flag so pausing and starting again (e.g. when scrubbing or looping) won't go back to fullscreen. // Also set the controls attribute so the user will be able to control playback. @@ -8021,9 +8026,6 @@ void HTMLMediaElement::exitFullscreen() setChangingVideoFullscreenMode(true); - if (!paused() && protect(document())->quirks().needsPauseBeforeFullscreenExitQuirk()) - pauseInternal(); - if (isInWindowOrStandardFullscreen(oldVideoFullscreenMode)) { setFullscreenMode(VideoFullscreenModeNone); // The exit fullscreen request will be sent in dispatchEvent(). diff --git a/Source/WebCore/html/HTMLMediaElement.h b/Source/WebCore/html/HTMLMediaElement.h index c6dc7b4fbdd0..42ac5d8adc80 100644 --- a/Source/WebCore/html/HTMLMediaElement.h +++ b/Source/WebCore/html/HTMLMediaElement.h @@ -1024,7 +1024,7 @@ class HTMLMediaElement // These "internal" functions do not check user gesture restrictions. void playInternal(); - void pauseInternal(); + void pauseInternal(bool dispatchPauseEvent = true); void completePlayInternal(); enum class IsExplicitLoad : bool { No, Yes }; diff --git a/Source/WebCore/page/QuirkNames.h b/Source/WebCore/page/QuirkNames.h index 993de4021f59..39faf0bfb371 100644 --- a/Source/WebCore/page/QuirkNames.h +++ b/Source/WebCore/page/QuirkNames.h @@ -125,7 +125,7 @@ enum class SiteSpecificQuirk { NeedsNavigatorUserAgentDataQuirk, NeedsNowPlayingFullscreenSwapQuirk, #if PLATFORM(IOS_FAMILY) - NeedsPauseBeforeFullscreenExitQuirk, + NeedsSuppressedPauseEventOnFullscreenExitQuirk, NeedsPreloadAutoQuirk, #endif #if PLATFORM(MAC) diff --git a/Source/WebCore/page/QuirkTable.cpp b/Source/WebCore/page/QuirkTable.cpp index 4a624a31a065..ac8e53b9586a 100644 --- a/Source/WebCore/page/QuirkTable.cpp +++ b/Source/WebCore/page/QuirkTable.cpp @@ -257,8 +257,8 @@ static constexpr Quirk table[] = { { .match = QuirkMatch::domain("espn.com"_s), .behaviors = { #if PLATFORM(IOS) - // espn.com rdar://154903596 - NeedsPauseBeforeFullscreenExitQuirk, + // espn.com rdar://184169028 + NeedsSuppressedPauseEventOnFullscreenExitQuirk, #endif #if PLATFORM(IOS) || PLATFORM(VISION) // espn.com rdar://problem/95651814 diff --git a/Source/WebCore/page/Quirks.cpp b/Source/WebCore/page/Quirks.cpp index 2dbb67f19e07..bf44504f6b43 100644 --- a/Source/WebCore/page/Quirks.cpp +++ b/Source/WebCore/page/Quirks.cpp @@ -1069,13 +1069,13 @@ bool Quirks::needsPreloadAutoQuirk() const #endif } -// espn.com rdar://154903596 -bool Quirks::needsPauseBeforeFullscreenExitQuirk() const +// espn.com rdar://184169028 +bool Quirks::needsSuppressedPauseEventOnFullscreenExitQuirk() const { #if PLATFORM(IOS_FAMILY) QUIRKS_EARLY_RETURN_IF_DISABLED_WITH_VALUE(false); - return m_quirksData.quirkIsEnabled(SiteSpecificQuirk::NeedsPauseBeforeFullscreenExitQuirk); + return m_quirksData.quirkIsEnabled(SiteSpecificQuirk::NeedsSuppressedPauseEventOnFullscreenExitQuirk); #else return false; #endif diff --git a/Source/WebCore/page/Quirks.h b/Source/WebCore/page/Quirks.h index a62985ef3a35..862d8d87ab84 100644 --- a/Source/WebCore/page/Quirks.h +++ b/Source/WebCore/page/Quirks.h @@ -167,7 +167,7 @@ class Quirks { bool NODELETE needsPreloadAutoQuirk() const; - bool NODELETE needsPauseBeforeFullscreenExitQuirk() const; + bool NODELETE needsSuppressedPauseEventOnFullscreenExitQuirk() const; bool shouldBypassBackForwardCache() const; bool shouldBypassAsyncScriptDeferring() const; From 64e677b027a4775149d55e010cd991024ea4a979 Mon Sep 17 00:00:00 2001 From: Alan Baradlay Date: Fri, 28 Aug 2026 09:16:51 -0700 Subject: [PATCH 044/103] [css-text-decor-4] Support percentages in text-decoration-inset https://bugs.webkit.org/show_bug.cgi?id=322441 Reviewed by Antti Koivisto. Widen text-decoration-inset from {1,2} to {1,2}, per the CSSWG resolution on csswg-drafts#8403. A percentage resolves against the inline size of the decorating box for box-decoration-break: slice, or of the individual box fragment for clone. Since a sliced box's inline size spans all of its fragments, TextBoxPainter now sums them (DecoratingBoxFragmentInlineSizes) instead of measuring the fragment being painted. That sum also gives the inline size preceding/following this fragment, so a trimming inset larger than one fragment now carries over into the next - a middle fragment can be trimmed, or vanish, without holding either endpoint of the box. Ink overflow cannot resolve these percentages while building a line, because the basis is not known until every fragment exists. InlineContentBuilder therefore collects the decorating boxes whose inset may resolve negative while it walks the display boxes, and adds the outward overhang in a follow-up pass over the finished lines. StyleDifference compares insets with no basis, where two different percentages would look equal, so a change involving one now requires layout outright. Tests: imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-026-ref.html imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-027-ref.html imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-028-ref.html imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-029-ref.html imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-multiline-ref.html imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-ref.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline.html imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice.html * LayoutTests/TestExpectations: text-decoration-inset-029 sets unprefixed box-decoration-break, which WebKit does not support, so it slices rather than clones; it passes once prefixed. 009/014 carry fuzzy annotations for their fractional endpoints instead of expectations. * LayoutTests/imported/w3c/resources/resource-files.json: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/w3c-import.log: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid-expected.txt: Re-import the upstream parsing tests, which now cover percentages and calc(). * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-009.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-014.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-026-ref.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-027-ref.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-028-ref.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-029-ref.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-ref.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-multiline-ref.html: Added. Import the upstream percentage tests. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence-expected.html: Added. 25% of a 200px box paints as 50px. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding-expected.html: Added. The basis includes the decorating box's padding and every run inside it, not just the painted run. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone-expected.html: Added. Per-fragment basis, via -webkit-box-decoration-break. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow-expected.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental-expected.html: Added. The outward overhang of a negative percentage, across compositing, line wrapping, propagation to a descendant, and dynamic/incremental relayout. * Source/WebCore/css/CSSProperties.json: {1,2}. * Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp: (WebCore::LayoutIntegration::InlineContentBuilder::adjustDisplayLines const): (WebCore::LayoutIntegration::InlineContentBuilder::adjustInkOverflowForPercentageTextDecorationInsets const): * Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h: * Source/WebCore/rendering/TextBoxPainter.cpp: (WebCore::decoratingBoxFragmentInlineSizes): (WebCore::TextBoxPainter::insetAdjustedDecorationLocationAndWidth const): (WebCore::DecoratingBoxFragmentInlineSizes::total const): * Source/WebCore/style/InlineTextBoxStyle.cpp: (WebCore::computedInkOverflowForDecorations): * Source/WebCore/style/StyleDifference.cpp: * Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.cpp: (WebCore::Style::TextDecorationInset::hasPercentage const): (WebCore::Style::TextDecorationInset::hasNegativePercentage const): (WebCore::Style::TextDecorationInset::resolvedStart const): (WebCore::Style::TextDecorationInset::resolvedEnd const): (WebCore::Style::TextDecorationInset::outwardExtent const): (WebCore::Style::CSSValueConversion::operator): * Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.h: (WTF::MarkableTraits::isEmptyValue): (WTF::MarkableTraits::emptyValue): (WebCore::Style::TextDecorationInset::TextDecorationInset): Canonical link: https://commits.webkit.org/320052@main --- LayoutTests/TestExpectations | 6 +- .../w3c/resources/resource-files.json | 6 ++ ...ext-decoration-inset-computed-expected.txt | 6 ++ .../text-decoration-inset-computed.html | 8 ++ ...text-decoration-inset-invalid-expected.txt | 2 +- .../text-decoration-inset-invalid.html | 4 +- .../text-decoration-inset-valid-expected.txt | 6 ++ .../parsing/text-decoration-inset-valid.html | 8 ++ .../text-decoration-inset-026-ref.html | 16 ++++ .../text-decoration-inset-027-ref.html | 16 ++++ .../text-decoration-inset-028-ref.html | 18 ++++ .../text-decoration-inset-029-ref.html | 18 ++++ ...-inset-percentage-slice-multiline-ref.html | 16 ++++ ...decoration-inset-percentage-slice-ref.html | 14 +++ .../text-decoration-inset-009.html | 1 + .../text-decoration-inset-014.html | 1 + .../text-decoration-inset-026-expected.html | 16 ++++ .../text-decoration-inset-026.html | 21 +++++ .../text-decoration-inset-027-expected.html | 16 ++++ .../text-decoration-inset-027.html | 21 +++++ .../text-decoration-inset-028-expected.html | 18 ++++ .../text-decoration-inset-028.html | 22 +++++ .../text-decoration-inset-029-expected.html | 18 ++++ .../text-decoration-inset-029.html | 22 +++++ .../text-decoration-inset-030-expected.html | 18 ++++ .../text-decoration-inset-030.html | 20 +++++ ...ation-inset-percentage-clone-expected.html | 16 ++++ ...ext-decoration-inset-percentage-clone.html | 23 +++++ ...inset-percentage-equivalence-expected.html | 16 ++++ ...coration-inset-percentage-equivalence.html | 21 +++++ ...on-inset-percentage-negative-expected.html | 16 ++++ ...set-percentage-negative-grow-expected.html | 17 ++++ ...ration-inset-percentage-negative-grow.html | 35 ++++++++ ...centage-negative-incremental-expected.html | 18 ++++ ...inset-percentage-negative-incremental.html | 35 ++++++++ ...et-percentage-negative-layer-expected.html | 17 ++++ ...ation-inset-percentage-negative-layer.html | 22 +++++ ...ercentage-negative-multiline-expected.html | 18 ++++ ...n-inset-percentage-negative-multiline.html | 25 ++++++ ...rcentage-negative-propagated-expected.html | 20 +++++ ...-inset-percentage-negative-propagated.html | 26 ++++++ ...-percentage-negative-repaint-expected.html | 16 ++++ ...ion-inset-percentage-negative-repaint.html | 33 +++++++ ...-decoration-inset-percentage-negative.html | 21 +++++ ...ion-inset-percentage-padding-expected.html | 21 +++++ ...t-decoration-inset-percentage-padding.html | 27 ++++++ ...ation-inset-percentage-slice-expected.html | 14 +++ ...t-percentage-slice-multiline-expected.html | 16 ++++ ...tion-inset-percentage-slice-multiline.html | 25 ++++++ ...ext-decoration-inset-percentage-slice.html | 22 +++++ .../css/css-text-decor/w3c-import.log | 14 +++ Source/WebCore/css/CSSProperties.json | 2 +- .../LayoutIntegrationInlineContentBuilder.cpp | 88 +++++++++++++++++++ .../LayoutIntegrationInlineContentBuilder.h | 2 + Source/WebCore/rendering/TextBoxPainter.cpp | 77 +++++++++------- Source/WebCore/style/InlineTextBoxStyle.cpp | 2 +- Source/WebCore/style/StyleDifference.cpp | 5 ++ .../StyleTextDecorationInset.cpp | 45 ++++++++-- .../StyleTextDecorationInset.h | 38 +++++--- 59 files changed, 1075 insertions(+), 56 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-026-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-027-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-028-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-029-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-multiline-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice.html diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index 148f3ff4168c..accb808eea6d 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -4874,9 +4874,9 @@ webkit.org/b/244813 imported/w3c/web-platform-tests/css/css-text-decor/text-deco imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-thickness-length-rounding-001.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-thickness-length-rounding-002.html [ ImageOnlyFailure ] -# fractional-position endpoint rounds up to a device pixel -webkit.org/b/244813 imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-009.html [ ImageOnlyFailure ] -webkit.org/b/244813 imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-014.html [ ImageOnlyFailure ] +# Not a text-decoration-inset bug: the test sets unprefixed 'box-decoration-break', which WebKit does not support (only -webkit-box-decoration-break), so the box is sliced rather than cloned. +# The test passes once the declaration is prefixed. +imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html [ ImageOnlyFailure ] # Not a text-decoration-inset bug: a pre-existing ~1px vertical decoration-position difference in a # columns:2 multicol context with inline vertical borders. Reproduces with the feature disabled. diff --git a/LayoutTests/imported/w3c/resources/resource-files.json b/LayoutTests/imported/w3c/resources/resource-files.json index 92e97d0cd3a5..93ef4ea00e45 100644 --- a/LayoutTests/imported/w3c/resources/resource-files.json +++ b/LayoutTests/imported/w3c/resources/resource-files.json @@ -8406,7 +8406,13 @@ "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-023-ref.html", "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-024-ref.html", "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-025-ref.html", + "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-026-ref.html", + "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-027-ref.html", + "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-028-ref.html", + "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-029-ref.html", "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-orthogonal-block-001-ref.html", + "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-multiline-ref.html", + "web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-ref.html", "web-platform-tests/css/css-text-decor/reference/text-decoration-line-010-ref.xht", "web-platform-tests/css/css-text-decor/reference/text-decoration-line-011-ref.xht", "web-platform-tests/css/css-text-decor/reference/text-decoration-line-012-ref.xht", diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed-expected.txt index 49ace8ee9180..04fdd737fcc9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed-expected.txt @@ -9,4 +9,10 @@ PASS Property text-decoration-inset value '1px 2px' PASS Property text-decoration-inset value '1ch -1ch' PASS Property text-decoration-inset value 'calc(1em / 4) calc(-1ch)' PASS Property text-decoration-inset value 'auto' +PASS Property text-decoration-inset value '10%' +PASS Property text-decoration-inset value '0 20%' +PASS Property text-decoration-inset value '10% 20%' +PASS Property text-decoration-inset value '10px -20%' +PASS Property text-decoration-inset value 'calc(10% - 1em) 0' +PASS Property text-decoration-inset value 'calc(10% + 1ch) calc(-20%)' diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed.html index 692d6b95e246..7d7ac4d39f6e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-computed.html @@ -28,5 +28,13 @@ test_computed_value("text-decoration-inset", "1ch -1ch", "20px -20px"); test_computed_value("text-decoration-inset", "calc(1em / 4) calc(-1ch)", "5px -20px"); test_computed_value("text-decoration-inset", "auto"); + + // https://github.com/w3c/csswg-drafts/issues/8403 + test_computed_value("text-decoration-inset", "10%"); + test_computed_value("text-decoration-inset", "0 20%", "0px 20%"); + test_computed_value("text-decoration-inset", "10% 20%"); + test_computed_value("text-decoration-inset", "10px -20%"); + test_computed_value("text-decoration-inset", "calc(10% - 1em) 0", "calc(10% - 20px) 0px"); + test_computed_value("text-decoration-inset", "calc(10% + 1ch) calc(-20%)", "calc(10% + 20px) -20%"); }); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid-expected.txt index 5fbfbfdfb75d..187446944e08 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid-expected.txt @@ -6,6 +6,6 @@ PASS e.style['text-decoration-inset'] = "0 auto" should not set the property val PASS e.style['text-decoration-inset'] = "auto none" should not set the property value PASS e.style['text-decoration-inset'] = "1px auto" should not set the property value PASS e.style['text-decoration-inset'] = "auto -1px" should not set the property value -PASS e.style['text-decoration-inset'] = "10%" should not set the property value +PASS e.style['text-decoration-inset'] = "10% auto" should not set the property value PASS e.style['text-decoration-inset'] = "45deg" should not set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid.html index aecc8e35767b..e903ed10792a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-invalid.html @@ -3,7 +3,7 @@ CSS Text Decoration Test: Parsing text-decoration-inset with invalid values - + @@ -15,6 +15,6 @@ test_invalid_value("text-decoration-inset", "auto none"); test_invalid_value("text-decoration-inset", "1px auto"); test_invalid_value("text-decoration-inset", "auto -1px"); -test_invalid_value("text-decoration-inset", "10%"); +test_invalid_value("text-decoration-inset", "10% auto"); test_invalid_value("text-decoration-inset", "45deg"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid-expected.txt index 115049c2bf6a..3e58f6959504 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid-expected.txt @@ -9,4 +9,10 @@ PASS e.style['text-decoration-inset'] = "1px 2px" should set the property value PASS e.style['text-decoration-inset'] = "1ch -1ch" should set the property value PASS e.style['text-decoration-inset'] = "calc(1em / 4) calc(-1ch)" should set the property value PASS e.style['text-decoration-inset'] = "auto" should set the property value +PASS e.style['text-decoration-inset'] = "10%" should set the property value +PASS e.style['text-decoration-inset'] = "0 20%" should set the property value +PASS e.style['text-decoration-inset'] = "10% 20%" should set the property value +PASS e.style['text-decoration-inset'] = "10px -20%" should set the property value +PASS e.style['text-decoration-inset'] = "calc(10% - 1em) 0" should set the property value +PASS e.style['text-decoration-inset'] = "calc(10% + 1ch) calc(-20%)" should set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid.html index 59ea5632865c..f402ca8183ef 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/parsing/text-decoration-inset-valid.html @@ -18,4 +18,12 @@ test_valid_value("text-decoration-inset", "1ch -1ch"); test_valid_value("text-decoration-inset", "calc(1em / 4) calc(-1ch)", "calc(0.25em) calc(-1ch)"); test_valid_value("text-decoration-inset", "auto"); + +// https://github.com/w3c/csswg-drafts/issues/8403 +test_valid_value("text-decoration-inset", "10%"); +test_valid_value("text-decoration-inset", "0 20%", "0px 20%"); +test_valid_value("text-decoration-inset", "10% 20%"); +test_valid_value("text-decoration-inset", "10px -20%"); +test_valid_value("text-decoration-inset", "calc(10% - 1em) 0", "calc(10% - 1em) 0px"); +test_valid_value("text-decoration-inset", "calc(10% + 1ch) calc(-20%)"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-026-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-026-ref.html new file mode 100644 index 000000000000..edc00dea6e0b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-026-ref.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset percentage + + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-027-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-027-ref.html new file mode 100644 index 000000000000..4ef861117d64 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-027-ref.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset calc + + + + +

abcdefghij  

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-028-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-028-ref.html new file mode 100644 index 000000000000..ab565c274e7b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-028-ref.html @@ -0,0 +1,18 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset percentage with b-d-b slice + + + + +
+

abcde fghij

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-029-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-029-ref.html new file mode 100644 index 000000000000..2411a16c7519 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-029-ref.html @@ -0,0 +1,18 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset percentage with b-d-b clone + + + + +
+

abcde fghij

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-multiline-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-multiline-ref.html new file mode 100644 index 000000000000..3ac42704c055 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-multiline-ref.html @@ -0,0 +1,16 @@ + + +Reference: percentage text-decoration-inset over three line fragments + + +

abcde fghij klmno

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-ref.html new file mode 100644 index 000000000000..04c43d55866a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/reference/text-decoration-inset-percentage-slice-ref.html @@ -0,0 +1,14 @@ + + +Reference: percentage text-decoration-inset resolves against the whole decorated run with box-decoration-break: slice + +
ABCD
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-009.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-009.html index 3f4c229e67d9..a519a5ab02e5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-009.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-009.html @@ -1,5 +1,6 @@ + CSS Text Decoration 4: text-decoration-inset diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-014.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-014.html index ea33cea453e5..7b390a9d6030 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-014.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-014.html @@ -1,5 +1,6 @@ + CSS Text Decoration 4: text-decoration-inset diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026-expected.html new file mode 100644 index 000000000000..edc00dea6e0b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026-expected.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset percentage + + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026.html new file mode 100644 index 000000000000..b8499b43e0a3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026.html @@ -0,0 +1,21 @@ + + +CSS Text Decoration 4: text-decoration-inset percentage + + + + + + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027-expected.html new file mode 100644 index 000000000000..4ef861117d64 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027-expected.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset calc + + + + +

abcdefghij  

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027.html new file mode 100644 index 000000000000..8bb94fe9fe49 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027.html @@ -0,0 +1,21 @@ + + +CSS Text Decoration 4: text-decoration-inset calc + + + + + + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028-expected.html new file mode 100644 index 000000000000..ab565c274e7b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028-expected.html @@ -0,0 +1,18 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset percentage with b-d-b slice + + + + +
+

abcde fghij

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028.html new file mode 100644 index 000000000000..5360d0e986f2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028.html @@ -0,0 +1,22 @@ + + +CSS Text Decoration 4: text-decoration-inset percentage with b-d-b slice + + + + + + +
+

abcde fghij

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029-expected.html new file mode 100644 index 000000000000..2411a16c7519 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029-expected.html @@ -0,0 +1,18 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset percentage with b-d-b clone + + + + +
+

abcde fghij

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html new file mode 100644 index 000000000000..d8e512dfafd3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html @@ -0,0 +1,22 @@ + + +CSS Text Decoration 4: text-decoration-inset percentage with b-d-b clone + + + + + + +
+

abcde fghij

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030-expected.html new file mode 100644 index 000000000000..ab565c274e7b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030-expected.html @@ -0,0 +1,18 @@ + + +CSS Text Decoration 4 reference: text-decoration-inset percentage with b-d-b slice + + + + +
+

abcde fghij

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030.html new file mode 100644 index 000000000000..7ffe4c77752b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030.html @@ -0,0 +1,20 @@ + + +CSS Text Decoration 4: text-decoration-inset percentage with b-d-b slice and br + + + + + + +

abcde
fghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone-expected.html new file mode 100644 index 000000000000..1fb80ae660c9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone-expected.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration: with box-decoration-break: clone a percentage inset resolves per fragment (reference) + + + +
abcde fghij
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone.html new file mode 100644 index 000000000000..fe1541738e31 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-clone.html @@ -0,0 +1,23 @@ + + +CSS Text Decoration: with box-decoration-break: clone a percentage inset resolves per fragment + + + + + + + + + +
abcde fghij
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence-expected.html new file mode 100644 index 000000000000..f4fe7cda170c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence-expected.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration: a percentage text-decoration-inset matches the equivalent length (reference) + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence.html new file mode 100644 index 000000000000..45a0c7fce0fa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-equivalence.html @@ -0,0 +1,21 @@ + + +CSS Text Decoration: a percentage text-decoration-inset matches the equivalent length + + + + + + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-expected.html new file mode 100644 index 000000000000..af15780a9f19 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-expected.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration: a negative percentage text-decoration-inset extends the decoration (reference) + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow-expected.html new file mode 100644 index 000000000000..ec38f4be666d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow-expected.html @@ -0,0 +1,17 @@ + + +CSS Text Decoration: growing a negative percentage inset re-expands the ink overflow (reference) + + + +
abcdefghij
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow.html new file mode 100644 index 000000000000..a6c539b51a4b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-grow.html @@ -0,0 +1,35 @@ + + + +CSS Text Decoration: growing a negative percentage inset re-expands the ink overflow + + + + + + + + +
abcdefghij
+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental-expected.html new file mode 100644 index 000000000000..b1b69b7bc02b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental-expected.html @@ -0,0 +1,18 @@ + + +CSS Text Decoration: a negative percentage inset survives an incremental relayout of a later line (reference) + + + +

abcde fghij klmno

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental.html new file mode 100644 index 000000000000..f951ae477231 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-incremental.html @@ -0,0 +1,35 @@ + + + +CSS Text Decoration: a negative percentage inset survives an incremental relayout of a later line + + + + + + + + +

abcde fghij klmnX

+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer-expected.html new file mode 100644 index 000000000000..c75c2ac70165 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer-expected.html @@ -0,0 +1,17 @@ + + +CSS Text Decoration: a negative percentage inset is not clipped by a composited layer (reference) + + + +
abcdefghij
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer.html new file mode 100644 index 000000000000..99184f5a323d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-layer.html @@ -0,0 +1,22 @@ + + +CSS Text Decoration: a negative percentage inset is not clipped by a composited layer + + + + + + + + +
abcdefghij
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline-expected.html new file mode 100644 index 000000000000..d51cd255ec85 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline-expected.html @@ -0,0 +1,18 @@ + + +CSS Text Decoration: a negative percentage inset on a multi-line box extends by a percentage of the whole box (reference) + + + +

abcde fghij klmno

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline.html new file mode 100644 index 000000000000..9aa846ed3aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-multiline.html @@ -0,0 +1,25 @@ + + +CSS Text Decoration: a negative percentage inset on a multi-line box extends by a percentage of the whole box + + + + + + + + +

abcde fghij klmno

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated-expected.html new file mode 100644 index 000000000000..eebe60bde18e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated-expected.html @@ -0,0 +1,20 @@ + + +CSS Text Decoration: a negative percentage inset extends a decoration that propagates to a descendant (reference) + + + + +
abcdefghij
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated.html new file mode 100644 index 000000000000..cb71af656148 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-propagated.html @@ -0,0 +1,26 @@ + + +CSS Text Decoration: a negative percentage inset extends a decoration that propagates to a descendant + + + + + + + + +
abcdefghij
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint-expected.html new file mode 100644 index 000000000000..ac4e5739619f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint-expected.html @@ -0,0 +1,16 @@ + + +CSS Text Decoration: a negative percentage inset repaints its overhang on a dynamic change (reference) + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint.html new file mode 100644 index 000000000000..23534aa241bf --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative-repaint.html @@ -0,0 +1,33 @@ + + + +CSS Text Decoration: a negative percentage inset repaints its overhang on a dynamic change + + + + + + + + +

abcdefghij

+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative.html new file mode 100644 index 000000000000..15b8ca64d260 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-negative.html @@ -0,0 +1,21 @@ + + +CSS Text Decoration: a negative percentage text-decoration-inset extends the decoration + + + + + + + + +

abcdefghij

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding-expected.html new file mode 100644 index 000000000000..38a7b74b95a9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding-expected.html @@ -0,0 +1,21 @@ + + +CSS Text Decoration: a percentage inset resolves against the decorating box including its padding (reference) + + + + + +
aaaabbbb
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding.html new file mode 100644 index 000000000000..8d693e1a6160 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-padding.html @@ -0,0 +1,27 @@ + + +CSS Text Decoration: a percentage inset resolves against the decorating box including its padding + + + + + + + + +
aaaabbbb
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-expected.html new file mode 100644 index 000000000000..04c43d55866a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-expected.html @@ -0,0 +1,14 @@ + + +Reference: percentage text-decoration-inset resolves against the whole decorated run with box-decoration-break: slice + +
ABCD
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline-expected.html new file mode 100644 index 000000000000..3ac42704c055 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline-expected.html @@ -0,0 +1,16 @@ + + +Reference: percentage text-decoration-inset over three line fragments + + +

abcde fghij klmno

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline.html new file mode 100644 index 000000000000..63d1166959c3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline.html @@ -0,0 +1,25 @@ + + +CSS Text Decoration 4: percentage text-decoration-inset over three line fragments + + + + + + +

abcde fghij klmno

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice.html new file mode 100644 index 000000000000..6587a92fbf03 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice.html @@ -0,0 +1,22 @@ + + + +CSS Text Decoration 4: percentage text-decoration-inset resolves against the whole decorated run with box-decoration-break: slice + + + + + +
ABCD
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/w3c-import.log index 06cd8872a6ba..cb15c4f39bdb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/w3c-import.log @@ -89,8 +89,22 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-024.html /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-025-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-025.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-026.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-027.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-028.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-029.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-030.html /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-orthogonal-block-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-orthogonal-block-001.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice-multiline.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-inset-percentage-slice.html /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-line-010-expected.xht /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-line-010.xht /LayoutTests/imported/w3c/web-platform-tests/css/css-text-decor/text-decoration-line-011-expected.xht diff --git a/Source/WebCore/css/CSSProperties.json b/Source/WebCore/css/CSSProperties.json index 1e9516b9212e..33a397c4e812 100644 --- a/Source/WebCore/css/CSSProperties.json +++ b/Source/WebCore/css/CSSProperties.json @@ -12098,7 +12098,7 @@ "computed-style-storage-path": ["m_nonInheritedData", "rareData"], "computed-style-storage-kind": "reference", "computed-style-type": "Style::TextDecorationInset", - "parser-grammar": "auto | {1,2}@(type=CSSValuePair default=previous)", + "parser-grammar": "auto | {1,2}@(type=CSSValuePair default=previous)", "applies-to-highlight-pseudo-elements": "yes" }, "specification": { diff --git a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp index 5e855cb84bde..8b8501677f5a 100644 --- a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp +++ b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp @@ -141,6 +141,7 @@ void InlineContentBuilder::adjustDisplayLines(InlineContent& inlineContent, size auto blockScrollableOverflowRect = FloatRect { }; auto blockInkOverflowRect = FloatRect { }; + auto decoratingBoxesWithNegativePercentageInset = DecoratingBoxes { }; for (size_t lineIndex = 0; lineIndex < startIndex; ++lineIndex) { auto& line = lines[lineIndex]; @@ -188,6 +189,13 @@ void InlineContentBuilder::adjustDisplayLines(InlineContent& inlineContent, size auto lastTextBoxIndex = std::optional { }; for (; boxIndex < boxes.size() && boxes[boxIndex].lineIndex() == lineIndex; ++boxIndex) { auto& box = boxes[boxIndex]; + + if (box.isInlineBox()) { + CheckedRef decoratingBoxStyle = box.style(); + if (decoratingBoxStyle->textDecorationLineInEffect() && decoratingBoxStyle->textDecorationInset().hasNegativePercentage()) + decoratingBoxesWithNegativePercentageInset.add(CheckedRef { box.layoutBox() }); + } + if (box.isRootInlineBox() || box.isEllipsis() || box.isLineBreak()) continue; @@ -256,6 +264,86 @@ void InlineContentBuilder::adjustDisplayLines(InlineContent& inlineContent, size } inlineContent.setScrollableOverflow(blockScrollableOverflowRect); inlineContent.setInkOverflow(blockInkOverflowRect); + + if (!decoratingBoxesWithNegativePercentageInset.isEmpty()) + adjustInkOverflowForPercentageTextDecorationInsets(inlineContent, startIndex, decoratingBoxesWithNegativePercentageInset); +} + +void InlineContentBuilder::adjustInkOverflowForPercentageTextDecorationInsets(InlineContent& inlineContent, size_t startIndex, const DecoratingBoxes& decoratingBoxes) const +{ + // A negative 'text-decoration-inset' extends the decoration past the text box, and that overhang has to be in the ink overflow or it gets clipped. + // When the inset is a percentage it resolves against the inline size of the whole decorating box (for box-decoration-break: slice), which spans every fragment + // of that box and so is only known once all the lines are built. + auto& lines = inlineContent.displayContent().lines; + auto& boxes = inlineContent.displayContent().boxes; + auto isHorizontalWritingMode = m_blockFlow.writingMode().isHorizontal(); + + auto inlineSizeForEachDecoratingBox = [&] { + auto inlineSizes = HashMap, float> { }; + for (auto& box : boxes) { + if (!box.isNonRootInlineBox()) + continue; + CheckedRef layoutBox { box.layoutBox() }; + if (!decoratingBoxes.contains(layoutBox)) + continue; + inlineSizes.add(layoutBox, 0.f).iterator->value += isHorizontalWritingMode ? box.width() : box.height(); + } + return inlineSizes; + }; + + auto outwardInsetForEachDecoratingBox = [&] { + auto inlineSizes = inlineSizeForEachDecoratingBox(); + auto outwardInsets = HashMap, float> { }; + for (auto& decoratingBox : decoratingBoxes) { + auto inlineSize = [&] { + if (decoratingBox->isInlineBox()) + return inlineSizes.getOptional(decoratingBox).value_or(0.f); + auto blockContainerInlineSize = 0.f; + for (auto& line : lines) + blockContainerInlineSize += line.contentLogicalWidth(); + return blockContainerInlineSize; + }(); + + CheckedRef style = decoratingBox->style(); + if (auto outwardInset = style->textDecorationInset().outwardExtent(style, inlineSize)) + outwardInsets.add(decoratingBox, outwardInset); + } + return outwardInsets; + }; + auto outwardInsets = outwardInsetForEachDecoratingBox(); + if (outwardInsets.isEmpty()) { + ASSERT_NOT_REACHED(); + return; + } + + auto blockInkOverflowRect = inlineContent.inkOverflow(); + auto boxIndex = !startIndex ? 0 : lines[startIndex - 1].lastBoxIndex() + 1; + for (size_t lineIndex = startIndex; lineIndex < lines.size(); ++lineIndex) { + auto& line = lines[lineIndex]; + + auto outwardInsetOnLine = [&] { + auto largestOutwardInset = 0.f; + for (; boxIndex < boxes.size() && boxes[boxIndex].lineIndex() == lineIndex; ++boxIndex) { + auto& box = boxes[boxIndex]; + if (!box.isInlineBox()) + continue; + if (auto outwardInset = outwardInsets.getOptional(CheckedRef { box.layoutBox() })) + largestOutwardInset = std::max(largestOutwardInset, *outwardInset); + } + return largestOutwardInset; + }; + + auto outwardInset = outwardInsetOnLine(); + if (!outwardInset) + continue; + + auto lineInkOverflowRect = line.inkOverflow(); + auto expansion = ceilf(outwardInset); + isHorizontalWritingMode ? lineInkOverflowRect.inflate(expansion, 0.f, expansion, 0.f) : lineInkOverflowRect.inflate(0.f, expansion, 0.f, expansion); + line.setInkOverflow(lineInkOverflowRect); + blockInkOverflowRect.unite(lineInkOverflowRect); + } + inlineContent.setInkOverflow(blockInkOverflowRect); } void InlineContentBuilder::computeIsFirstIsLastBoxAndBidiReorderingForInlineContent(InlineDisplay::Boxes& boxes) const diff --git a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h index 73d6e93e1e7c..6ea7e031e53e 100644 --- a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h +++ b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h @@ -46,6 +46,8 @@ class InlineContentBuilder { private: void adjustDisplayLines(InlineContent&, size_t startIndex) const; + using DecoratingBoxes = HashSet>; + void adjustInkOverflowForPercentageTextDecorationInsets(InlineContent&, size_t startIndex, const DecoratingBoxes&) const; void computeIsFirstIsLastBoxAndBidiReorderingForInlineContent(InlineDisplay::Boxes&) const; FloatRect handlePartialDisplayContentUpdate(Layout::InlineLayoutResult&&, InlineContent&, const Layout::InlineDamage*) const; diff --git a/Source/WebCore/rendering/TextBoxPainter.cpp b/Source/WebCore/rendering/TextBoxPainter.cpp index bbeaceb4fcc0..95856d60d4c1 100644 --- a/Source/WebCore/rendering/TextBoxPainter.cpp +++ b/Source/WebCore/rendering/TextBoxPainter.cpp @@ -34,6 +34,7 @@ #include "GraphicsContext.h" #include "HTMLAnchorElement.h" #include "InlineIteratorBoxInlines.h" +#include "InlineIteratorInlineBox.h" #include "InlineIteratorLineBox.h" #include "InlineIteratorTextBoxInlines.h" #include "InlineTextBoxStyle.h" @@ -993,6 +994,23 @@ static float autoTextDecorationInset(const Style::ComputedStyle& style) return style.computedFontSize() / 8; } +struct DecoratingBoxFragmentInlineSizes { + float preceding { 0.f }; + float current { 0.f }; + float following { 0.f }; + + float total() const { return preceding + current + following; } +}; +static DecoratingBoxFragmentInlineSizes decoratingBoxFragmentInlineSizes(const InlineIterator::InlineBox& decoratingInlineBox) +{ + auto inlineSizes = DecoratingBoxFragmentInlineSizes { .current = decoratingInlineBox.logicalWidth() }; + for (auto fragment = decoratingInlineBox.nextInlineBoxLineLeftward(); fragment; fragment.traverseInlineBoxLineLeftward()) + inlineSizes.preceding += fragment->logicalWidth(); + for (auto fragment = decoratingInlineBox.nextInlineBoxLineRightward(); fragment; fragment.traverseInlineBoxLineRightward()) + inlineSizes.following += fragment->logicalWidth(); + return inlineSizes; +} + std::pair TextBoxPainter::insetAdjustedDecorationLocationAndWidth(const DecoratingBox& decoratingBox, const StyledMarkedText& markedText) const { auto boxOrigin = decoratingBox.location; @@ -1008,55 +1026,54 @@ std::pair TextBoxPainter::insetAdjustedDecorationLocationAndW auto& inset = *insetStyles.inset; auto& style = decoratingBox.style.get(); + auto writingMode = style.writingMode(); + auto decoratingInlineBox = decoratingBox.inlineBox; + auto isSliced = insetStyles.boxDecorationBreak != BoxDecorationBreak::Clone; + + auto fragmentInlineSizes = isSliced ? decoratingBoxFragmentInlineSizes(*decoratingInlineBox) : DecoratingBoxFragmentInlineSizes { .current = decoratingInlineBox->logicalWidth() }; auto autoValue = inset.isAuto() ? autoTextDecorationInset(style) : 0.f; - auto startInset = inset.resolvedStart(style, autoValue); - auto endInset = inset.resolvedEnd(style, autoValue); + auto percentageBasis = fragmentInlineSizes.total(); + auto startInset = inset.resolvedStart(style, autoValue, percentageBasis); + auto endInset = inset.resolvedEnd(style, autoValue, percentageBasis); if (!startInset && !endInset) return { boxOrigin, width }; - auto writingMode = style.writingMode(); - auto decoratingInlineBox = decoratingBox.inlineBox; - // box-decoration-break: the start inset applies only to the first fragment's start edge and the // end inset only to the last fragment's end edge; for box-decoration-break: clone every fragment is // a complete box, so both endpoints are inset on every line. - auto closedEdges = [&]() -> RectEdges { - if (!decoratingInlineBox) - return { true }; - if (insetStyles.boxDecorationBreak == BoxDecorationBreak::Clone) - return { true }; - return decoratingInlineBox->closedEdges(); - }(); + auto closedEdges = isSliced ? decoratingInlineBox->closedEdges() : RectEdges(true); + auto hasLogicalStartEdge = closedEdges.start(writingMode); + auto hasLogicalEndEdge = closedEdges.end(writingMode); + + auto insetForFragment = [](float inset, float inlineSizeToBoxEdge, bool ownsEdge) { + if (inset > 0) + return std::max(0.f, inset - inlineSizeToBoxEdge); + return ownsEdge ? inset : 0.f; + }; + startInset = insetForFragment(startInset, fragmentInlineSizes.preceding, hasLogicalStartEdge); + endInset = insetForFragment(endInset, fragmentInlineSizes.following, hasLogicalEndEdge); + auto startEdgeOnFragment = hasLogicalStartEdge || startInset > 0; + auto endEdgeOnFragment = hasLogicalEndEdge || endInset > 0; bool isLTR = writingMode.isBidiLTR(); - // A decorating box can span several leaf boxes on a line (its bidi runs), each split into marked-text - // sub-ranges. Map the logical start/end insets onto the decoration's visual left/right edges; - // displacements below are measured rightward (a positive inset trims inward, a negative one extends - // outward). box-decoration-break decides which of the decoration's edges live on this line fragment, - // and firstLeafBox/lastLeafBox + the marked-text offsets decide which painted piece actually reaches - // that visual edge. auto textBox = makeIterator(); bool ownsLineLeftEdge = !decoratingInlineBox || textBox == decoratingInlineBox->firstLeafBox(); bool ownsLineRightEdge = !decoratingInlineBox || textBox == decoratingInlineBox->lastLeafBox(); bool ownsLogicalStart = !markedText.startOffset; bool ownsLogicalEnd = markedText.endOffset == m_paintTextRun.length(); - float visualLeftInset = isLTR ? startInset : endInset; - float visualRightInset = isLTR ? endInset : startInset; - bool leftEdgeOnFragment = isLTR ? closedEdges.start(writingMode) : closedEdges.end(writingMode); - bool rightEdgeOnFragment = isLTR ? closedEdges.end(writingMode) : closedEdges.start(writingMode); + auto visualLeftInset = isLTR ? startInset : endInset; + auto visualRightInset = isLTR ? endInset : startInset; + auto leftEdgeOnFragment = isLTR ? startEdgeOnFragment : endEdgeOnFragment; + auto rightEdgeOnFragment = isLTR ? endEdgeOnFragment : startEdgeOnFragment; float leftEdgeMove = leftEdgeOnFragment ? visualLeftInset : 0.f; float rightEdgeMove = rightEdgeOnFragment ? -visualRightInset : 0.f; - // When the whole decoration lives on this fragment, the part of the inset that moves both visual - // edges the same way is an inline-axis shift of the decoration as a whole. Applying that shift to - // every painted piece keeps a symmetric inset a rigid shift of the decoration - including the seam - // between bidi runs - instead of pinning that interior seam. The remaining per-edge movement is the - // extend/trim overhang, applied only at the piece that actually reaches that visual edge; interior - // pieces (e.g. superscripts/subscripts at other baselines) get only the whole-decoration shift, so - // they stay put for a pure extend/trim. (skip-ink needs no adjustment: its gaps are measured relative - // to the underline's bounding box, which already tracks boxOrigin.) + // The part of the inset that moves both visual edges the same way is a shift of the whole decoration, + // so every painted piece gets it and a symmetric inset stays rigid across bidi runs. + // The rest is the extend/trim overhang, which only the piece reaching that visual edge gets, leaving + // interior pieces (e.g. a superscript at another baseline) where they are for a pure extend/trim. float decorationInlineShift = (leftEdgeOnFragment && rightEdgeOnFragment) ? (leftEdgeMove + rightEdgeMove) / 2.f : 0.f; bool reachesVisualLeft = leftEdgeOnFragment && ownsLineLeftEdge && (isLTR ? ownsLogicalStart : ownsLogicalEnd); bool reachesVisualRight = rightEdgeOnFragment && ownsLineRightEdge && (isLTR ? ownsLogicalEnd : ownsLogicalStart); diff --git a/Source/WebCore/style/InlineTextBoxStyle.cpp b/Source/WebCore/style/InlineTextBoxStyle.cpp index acbf824fbac2..62d7f9a73c98 100644 --- a/Source/WebCore/style/InlineTextBoxStyle.cpp +++ b/Source/WebCore/style/InlineTextBoxStyle.cpp @@ -231,7 +231,7 @@ static InkOverflowForDecorations computedInkOverflowForDecorations(const Style:: // or it gets clipped / left unrepainted. A positive inset (and 'auto', which only trims inward) // needs no expansion. We expand both inline edges by the largest outward amount, which is a safe // superset regardless of writing mode / direction. - auto outwardInset = std::max({ 0.f, -lineStyle.textDecorationInset().resolvedStart(lineStyle, 0.f), -lineStyle.textDecorationInset().resolvedEnd(lineStyle, 0.f) }); + auto outwardInset = lineStyle.textDecorationInset().outwardExtent(lineStyle, 0.f); if (outwardInset) { overflowResult.left() = std::max(overflowResult.left(), LayoutUnit(ceilf(outwardInset))); overflowResult.right() = std::max(overflowResult.right(), LayoutUnit(ceilf(outwardInset))); diff --git a/Source/WebCore/style/StyleDifference.cpp b/Source/WebCore/style/StyleDifference.cpp index c5833e8e9709..1ad364cde4b9 100644 --- a/Source/WebCore/style/StyleDifference.cpp +++ b/Source/WebCore/style/StyleDifference.cpp @@ -127,6 +127,11 @@ class DifferenceFunctions final { if (isAlignedForUnder(a) || isAlignedForUnder(b)) return true; + // A percentage value resolves against the decorating box size, which is not known here, + // so two different percentages would compare equal below at inkOverflowForDecorations where percent values are resolved against 0. + if (a.textDecorationInset() != b.textDecorationInset() && (a.textDecorationInset().hasPercentage() || b.textDecorationInset().hasPercentage())) + return true; + if (inkOverflowForDecorations(a) != inkOverflowForDecorations(b)) return true; } diff --git a/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.cpp b/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.cpp index 437c9b1d3e87..820a5b4b80fd 100644 --- a/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.cpp +++ b/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.cpp @@ -36,28 +36,57 @@ namespace Style { using namespace CSS::Literals; -float TextDecorationInset::resolvedStart(const Style::ComputedStyle& style, float autoValue) const +bool TextDecorationInset::hasPercentage() const +{ + auto pair = tryValue(); + return pair && (pair->first().isPercentOrCalculated() || pair->second().isPercentOrCalculated()); +} + +bool TextDecorationInset::hasNegativePercentage() const +{ + auto pair = tryValue(); + if (!pair) + return false; + auto isNegativePercentage = [](const auto& endpoint) { + return endpoint.isPercentOrCalculated() && endpoint.isPossiblyNegative(); + }; + return isNegativePercentage(pair->first()) || isNegativePercentage(pair->second()); +} + +float TextDecorationInset::resolvedStart(const Style::ComputedStyle& style, float autoValue, float percentageBasis) const { if (auto pair = tryValue()) - return Style::evaluate(pair->first(), style.usedZoomForLength()); + return Style::evaluate(pair->first(), percentageBasis, style.usedZoomForLength()); return autoValue; } -float TextDecorationInset::resolvedEnd(const Style::ComputedStyle& style, float autoValue) const +float TextDecorationInset::resolvedEnd(const Style::ComputedStyle& style, float autoValue, float percentageBasis) const { if (auto pair = tryValue()) - return Style::evaluate(pair->second(), style.usedZoomForLength()); + return Style::evaluate(pair->second(), percentageBasis, style.usedZoomForLength()); return autoValue; } +float TextDecorationInset::outwardExtent(const Style::ComputedStyle& style, float percentageBasis) const +{ + auto pair = tryValue(); + if (!pair) + return 0.f; // 'auto' only ever trims inward. + + auto outwardExtent = [&](const auto& endpoint) { + return std::max(0.f, -Style::evaluate(endpoint, percentageBasis, style.usedZoomForLength())); + }; + return std::max(outwardExtent(pair->first()), outwardExtent(pair->second())); +} + // MARK: - Conversion auto CSSValueConversion::operator()(BuilderState& state, const CSSValue& value) -> TextDecorationInsetPair { - using Length = Style::Length<>; + using LengthPercentage = Style::LengthPercentage<>; if (RefPtr primitiveValue = dynamicDowncast(value)) { - auto length = toStyleFromCSSValue(state, *primitiveValue); + auto length = toStyleFromCSSValue(state, *primitiveValue); return { length, length }; } @@ -66,8 +95,8 @@ auto CSSValueConversion::operator()(BuilderState& state return { 0_css_px, 0_css_px }; return { - toStyleFromCSSValue(state, pair->first), - toStyleFromCSSValue(state, pair->second), + toStyleFromCSSValue(state, pair->first), + toStyleFromCSSValue(state, pair->second), }; } diff --git a/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.h b/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.h index 0c74244530a2..61eda6ec6596 100644 --- a/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.h +++ b/Source/WebCore/style/values/text-decoration/StyleTextDecorationInset.h @@ -29,25 +29,35 @@ namespace WebCore { namespace Style { -using TextDecorationInsetPair = MinimallySerializingSpaceSeparatedPair>; +using TextDecorationInsetPair = MinimallySerializingSpaceSeparatedPair>; } } -// text-decoration-inset stores 'auto' or a length pair, so ValueOrKeyword needs a Markable -// representation of the pair; the pair is empty exactly when its first length is empty. +// text-decoration-inset stores 'auto' or a length-percentage pair, so ValueOrKeyword needs a Markable +// representation of the pair; the pair is empty exactly when its first component holds the empty +// sentinel. namespace WTF { template<> struct MarkableTraits { using Pair = WebCore::Style::TextDecorationInsetPair; + using LengthPercentage = WebCore::Style::LengthPercentage<>; using Length = WebCore::Style::Length<>; - static bool isEmptyValue(const Pair& value) { return MarkableTraits::isEmptyValue(value.first()); } - static Pair emptyValue() { return { MarkableTraits::emptyValue(), MarkableTraits::emptyValue() }; } + + static bool isEmptyValue(const Pair& value) + { + auto length = value.first().tryDimension(); + return length && MarkableTraits::isEmptyValue(*length); + } + static Pair emptyValue() + { + return { LengthPercentage { MarkableTraits::emptyValue() }, LengthPercentage { MarkableTraits::emptyValue() } }; + } }; } namespace WebCore { namespace Style { -// <'text-decoration-inset'> = auto | {1,2} +// <'text-decoration-inset'> = auto | {1,2} // The first value applies to the start endpoint, the second to the end endpoint of the line // decorations; a single value applies to both. Positive values move an endpoint inward (trimming // the decoration), negative values move it outward (extending it). 'auto' lets the UA choose an @@ -58,16 +68,24 @@ struct TextDecorationInset : ValueOrKeyword literal) - : Base(Value { Length<> { literal }, Length<> { literal } }) + : Base(Value { LengthPercentage<> { literal }, LengthPercentage<> { literal } }) { } bool isAuto() const { return isKeyword(); } + bool hasPercentage() const; + bool hasNegativePercentage() const; // Resolves the start/end inset to used CSS pixels. For 'auto', returns the UA-chosen autoValue - // supplied by the caller (which depends on the decorating box's font). - float resolvedStart(const Style::ComputedStyle&, float autoValue) const; - float resolvedEnd(const Style::ComputedStyle&, float autoValue) const; + // supplied by the caller (which depends on the decorating box's font). Percentages resolve + // against percentageBasis, the inline size of the decorating box (for box-decoration-break: slice) + // or of the individual box fragment (for clone). + float resolvedStart(const Style::ComputedStyle&, float autoValue, float percentageBasis) const; + float resolvedEnd(const Style::ComputedStyle&, float autoValue, float percentageBasis) const; + + // How far a negative (extending) inset pushes the decoration past the text box, i.e. the outward + // overhang that has to be included in ink overflow. Returns 0 for 'auto' and for insets that only trim inward. + float outwardExtent(const Style::ComputedStyle&, float percentageBasis) const; }; // MARK: - Conversion From ad023b35ced0b26ac7fd8e2c86d2daa6ea8a0a16 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 09:31:38 -0700 Subject: [PATCH 045/103] [JSC][Wasm] Compare custom section names without allocating a String https://bugs.webkit.org/show_bug.cgi?id=322759 Reviewed by Yusuke Suzuki. WebAssembly.Module.customSections compared each section name by building a String. Use WTF::equal against the UTF-8 name span. * Source/JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp: Canonical link: https://commits.webkit.org/320053@main --- .../JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp index 72f16569fe84..28f11fb4c159 100644 --- a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp +++ b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp @@ -46,6 +46,7 @@ #include "WebAssemblyModulePrototype.h" #include #include +#include namespace JSC { static JSC_DECLARE_HOST_FUNCTION(webAssemblyModuleCustomSections); @@ -89,8 +90,7 @@ JSC_DEFINE_HOST_FUNCTION(webAssemblyModuleCustomSections, (JSGlobalObject* globa const auto& customSections = module->moduleInformation().customSections; for (const Wasm::CustomSection& section : customSections) { - // FIXME: Add a function that compares a String with a span so we don't need to make a string. - if (WTF::makeString(section.name) == sectionNameString) { + if (equal(sectionNameString, section.name.span())) { auto buffer = ArrayBuffer::tryCreate(section.payload.span()); if (!buffer) return JSValue::encode(throwException(globalObject, throwScope, createOutOfMemoryError(globalObject))); From 98007ce32048c750eb1a67543055cec022e4eef4 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 09:33:33 -0700 Subject: [PATCH 046/103] [JSC][Wasm] Inline ref.func https://bugs.webkit.org/show_bug.cgi?id=322530 Reviewed by Yusuke Suzuki. Store function wrappers in a trailing instance array and load the slot in BBQ and OMG. Call ensure only when the slot is empty. * Source/JavaScriptCore/b3/B3AbstractHeapRepository.h: * Source/JavaScriptCore/wasm/WasmBBQJIT.cpp: * Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp: * Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp: * Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h: * JSTests/wasm/stress/ref-func-wrapper-identity.js: Added. Canonical link: https://commits.webkit.org/320054@main --- .../wasm/stress/ref-func-wrapper-identity.js | 50 +++++++++++++++++++ .../b3/B3AbstractHeapRepository.h | 1 + Source/JavaScriptCore/wasm/WasmBBQJIT.cpp | 27 +++++++--- .../wasm/WasmOMGIRGenerator.cpp | 25 ++++++++-- .../wasm/js/JSWebAssemblyInstance.cpp | 22 ++++---- .../wasm/js/JSWebAssemblyInstance.h | 24 +++++++-- 6 files changed, 121 insertions(+), 28 deletions(-) create mode 100644 JSTests/wasm/stress/ref-func-wrapper-identity.js diff --git a/JSTests/wasm/stress/ref-func-wrapper-identity.js b/JSTests/wasm/stress/ref-func-wrapper-identity.js new file mode 100644 index 000000000000..59a1eae1bfa7 --- /dev/null +++ b/JSTests/wasm/stress/ref-func-wrapper-identity.js @@ -0,0 +1,50 @@ +import { instantiate } from "../wabt-wrapper.js" +import * as assert from "../assert.js" + +let producerWat = ` +(module + (func $hidden (export "hidden") (result i32) (i32.const 7)) +) +` + +let wat = ` +(module + (import "m" "hidden" (func $imported (result i32))) + (func $local (result i32) (i32.const 11)) + (func $exported (export "exported") (result i32) (i32.const 13)) + (elem declare funcref (ref.func $imported) (ref.func $local) (ref.func $exported)) + (func (export "importedRef") (result funcref) (ref.func $imported)) + (func (export "localRef") (result funcref) (ref.func $local)) + (func (export "exportedRef") (result funcref) (ref.func $exported)) +) +` + +async function test() { + const { hidden } = (await instantiate(producerWat)).exports + const { importedRef, localRef, exportedRef, exported } = (await instantiate(wat, { m: { hidden } })).exports + + let imported = importedRef() + let local = localRef() + let exportedFromRef = exportedRef() + + for (let i = 0; i < wasmTestLoopCount; ++i) { + assert.eq(importedRef(), imported) + assert.eq(localRef(), local) + assert.eq(exportedRef(), exportedFromRef) + } + + assert.eq(imported === local, false) + assert.eq(local === exportedFromRef, false) + assert.eq(imported, hidden) + assert.eq(exportedFromRef, exported) + assert.eq(imported(), 7) + assert.eq(local(), 11) + assert.eq(exportedFromRef(), 13) + + fullGC() + assert.eq(importedRef(), imported) + assert.eq(localRef(), local) + assert.eq(exportedRef(), exported) +} + +await assert.asyncTest(test()) diff --git a/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h b/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h index c38f2302d2b7..79b06bedc113 100644 --- a/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h +++ b/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h @@ -270,6 +270,7 @@ namespace JSC::B3 { macro(JSWebAssemblyInstance_gcObjectStructureIDs) \ macro(JSWebAssemblyInstance_importFunctionStubs) \ macro(JSWebAssemblyInstance_tables) \ + macro(JSWebAssemblyInstance_functionWrappers) \ // This class is meant to be cacheable between compilations, but it doesn't have to be. // Doing so saves on creation of nodes. But clearing it will save memory. diff --git a/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp b/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp index 77a7e16d2cc3..c5ab146354b4 100644 --- a/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp +++ b/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp @@ -3215,16 +3215,27 @@ PartialResult BBQJIT::addI32Extend8S(Value operand, Value& result) [[nodiscard]] PartialResult BBQJIT::addRefFunc(FunctionSpaceIndex index, Value& result) { - // FIXME: Emit this inline . - TypeKind returnType = TypeKind::Ref; + GPRReg resultGPR; + { + ScratchScope<1, 0> scratches(*this); + resultGPR = scratches.gpr(0); + + m_jit.load64(Address(GPRInfo::wasmContextInstancePointer, safeCast(JSWebAssemblyInstance::offsetOfFunctionWrapper(m_info, index))), resultGPR); + + JumpList slowPath = m_jit.branchTest64(ResultCondition::Zero, resultGPR); + MacroAssembler::Label done(m_jit); + m_slowPaths.append({ origin(), WTF::move(slowPath), WTF::move(done), copyBindings(), [index, resultGPR](BBQJIT&, CCallHelpers& jit) { + jit.prepareWasmCallOperation(GPRInfo::wasmContextInstancePointer); + jit.setupArguments(GPRInfo::wasmContextInstancePointer, TrustedImm32(static_cast(index))); + jit.callOperation(operationWasmRefFunc); + jit.move(GPRInfo::returnValueGPR, resultGPR); + } }); + } - Vector arguments = { - instanceValue(), - Value::fromI32(index) - }; - result = topValue(returnType); - emitCCall(&operationWasmRefFunc, arguments, result); + result = topValue(TypeKind::Ref); + bind(result, Location::fromGPR(resultGPR)); + LOG_INSTRUCTION("RefFunc", index, RESULT(result)); return { }; } diff --git a/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp index d487c5640138..e9cbe8ce157d 100644 --- a/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp +++ b/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp @@ -1786,9 +1786,28 @@ auto OMGIRGenerator::addTableSet(unsigned tableIndex, ExpressionType index, Expr auto OMGIRGenerator::addRefFunc(FunctionSpaceIndex index, ExpressionType& result) -> PartialResult { - // FIXME: Emit this inline . - result = push(callWasmOperation(m_currentBlock, wasmRefType(), operationWasmRefFunc, - instanceValue(), constant(toB3Type(Types::I32), index))); + auto* loaded = m_currentBlock->appendNew(m_proc, Load, wasmRefType(), origin(), instanceValue(), safeCast(JSWebAssemblyInstance::offsetOfFunctionWrapper(m_info, index))); + m_heaps.decorateMemory(&m_heaps.JSWebAssemblyInstance_functionWrappers[index], loaded); + + auto* slowPath = m_proc.addBlock(); + auto* continuation = m_proc.addBlock(); + auto* phi = continuation->appendNew(m_proc, Phi, wasmRefType(), origin()); + + m_currentBlock->appendNew(m_proc, origin(), loaded, phi); + m_currentBlock->appendNewControlValue(m_proc, B3::Branch, origin(), loaded, + FrequentedBlock(continuation), FrequentedBlock(slowPath, FrequencyClass::Rare)); + slowPath->addPredecessor(m_currentBlock); + continuation->addPredecessor(m_currentBlock); + + m_currentBlock = slowPath; + auto* called = callWasmOperation(m_currentBlock, wasmRefType(), operationWasmRefFunc, + instanceValue(), constant(Int32, index)); + m_currentBlock->appendNew(m_proc, origin(), called, phi); + m_currentBlock->appendNewControlValue(m_proc, Jump, origin(), continuation); + continuation->addPredecessor(m_currentBlock); + + m_currentBlock = continuation; + result = push(phi); TRACE_VALUE(Wasm::Types::Funcref, get(result), "ref_func ", index); return { }; } diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp index a4591d7f54c9..d027f1a253b2 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp @@ -136,6 +136,7 @@ JSWebAssemblyInstance::JSWebAssemblyInstance(VM& vm, Structure* structure, JSWeb } memset(reinterpret_cast(baselineDatas().data()), 0, baselineDatas().size_bytes()); + zeroSpan(asMutableByteSpan(functionWrappers())); if (m_moduleInformation->hasGCObjectTypes()) { memset(reinterpret_cast(gcObjectStructureIDs().data()), 0, gcObjectStructureIDs().size_bytes()); CompleteSubspace* subspace = JSWebAssemblyArray::subspaceFor(vm); @@ -222,9 +223,9 @@ void JSWebAssemblyInstance::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->gcObjectStructureID(i)); } - Locker locker { cell->cellLock() }; for (auto& wrapper : thisObject->functionWrappers()) - visitor.appendUnbarriered(wrapper.get()); + visitor.append(wrapper); + Locker locker { cell->cellLock() }; for (auto& entry : thisObject->m_constantExpressionValues) visitor.append(entry.value); for (auto& entry : thisObject->m_tagWrappers) @@ -308,9 +309,7 @@ Identifier JSWebAssemblyInstance::createPrivateModuleKey() size_t JSWebAssemblyInstance::allocationSize(const Wasm::ModuleInformation& info) { - if (info.hasGCObjectTypes()) - return offsetOfAllocatorForGCObject(info, MarkedSpace::numSizeClasses); - return offsetOfBaselineData(info, info.internalFunctionCount()); + return offsetOfFunctionWrapper(info, info.functionIndexSpaceSize()); } @@ -445,18 +444,17 @@ void JSWebAssemblyInstance::setGlobal(unsigned i, JSValue value) JSValue JSWebAssemblyInstance::getFunctionWrapper(unsigned i) const { - JSValue value = m_functionWrappers.get(i).get(); - if (value.isEmpty()) - return jsNull(); - return value; + ASSERT(i < functionWrappers().size()); + JSValue value = functionWrappers()[i].get(); + return value ? value : jsNull(); } void JSWebAssemblyInstance::setFunctionWrapper(unsigned i, JSValue value) { + ASSERT(i < functionWrappers().size()); ASSERT(value.isCallable()); - ASSERT(!m_functionWrappers.contains(i)); - Locker locker { cellLock() }; - m_functionWrappers.set(i, WriteBarrier(vm(), this, value)); + ASSERT(!functionWrappers()[i].get()); + functionWrappers()[i].set(vm(), this, value); ASSERT(getFunctionWrapper(i) == value); } diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h index 33477a44f6f8..c8c64e495111 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h @@ -73,7 +73,7 @@ class BaselineData; } // The layout of a JSWebAssemblyInstance is -// { struct JSWebAssemblyInstance }[ WasmMemoryBaseAndSize ][ WasmOrJSImportableFunctionCallLinkInfo ][ Wasm::Table* ][ Global::Value ][ Wasm::BaselineData* ][ WebAssemblyGCStructure* ][ Allocator* ] +// { struct JSWebAssemblyInstance }[ WasmMemoryBaseAndSize ][ WasmOrJSImportableFunctionCallLinkInfo ][ Wasm::Table* ][ Global::Value ][ Wasm::BaselineData* ][ WebAssemblyGCStructure* ][ Allocator* ][ WriteBarrier function wrappers ] // in a compound TrailingArray-like format. class JSWebAssemblyInstance final : public JSNonFinalObject { friend class LLIntOffsetsExtractor; @@ -182,8 +182,6 @@ class JSWebAssemblyInstance final : public JSNonFinalObject { static constexpr ptrdiff_t offsetOfVM() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_vm); } static constexpr ptrdiff_t offsetOfModuleRecord() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_moduleRecord); } - using FunctionWrapperMap = UncheckedKeyHashMap, IntHash, WTF::UnsignedWithZeroKeyHashTraits>; - static constexpr ptrdiff_t offsetOfSoftStackLimit() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_stackMirror) + StackManager::Mirror::offsetOfSoftStackLimit(); } Wasm::Module& module() const { return m_module.get(); } @@ -308,7 +306,6 @@ class JSWebAssemblyInstance final : public JSNonFinalObject { const BitVector& globalsToMark() LIFETIME_BOUND { return m_globalsToMark; } const BitVector& globalsToBinding() LIFETIME_BOUND { return m_globalsToBinding; } JSValue getFunctionWrapper(unsigned) const; - typename FunctionWrapperMap::ValuesConstIteratorRange functionWrappers() const { return m_functionWrappers.values(); } void setFunctionWrapper(unsigned, JSValue); JSValue ensureFunctionWrapper(Wasm::FunctionSpaceIndex); void setBuiltinCalleeBits(uint32_t builtinID, CalleeBits calleeBits) { m_builtinCalleeBits[builtinID] = calleeBits; } @@ -378,6 +375,14 @@ class JSWebAssemblyInstance final : public JSNonFinalObject { return roundUpToMultipleOf(offsetOfGCObjectStructureID(info, info.typeCount())) + sizeof(Allocator) * index; } + static ptrdiff_t offsetOfFunctionWrapper(const Wasm::ModuleInformation& info, unsigned index) + { + ptrdiff_t base = info.hasGCObjectTypes() + ? offsetOfAllocatorForGCObject(info, MarkedSpace::numSizeClasses) + : offsetOfBaselineData(info, info.internalFunctionCount()); + return roundUpToMultipleOf)>(base) + sizeof(WriteBarrier) * index; + } + static size_t offsetOfTargetInstance(const Wasm::ModuleInformation& info, size_t importFunctionNum) { return offsetOfImportFunctionInfo(info, importFunctionNum) + OBJECT_OFFSETOF(Wasm::WasmOrJSImportableFunctionCallLinkInfo, targetInstance); } static size_t offsetOfEntrypointLoadLocation(const Wasm::ModuleInformation& info, size_t importFunctionNum) { return offsetOfImportFunctionInfo(info, importFunctionNum) + OBJECT_OFFSETOF(Wasm::WasmOrJSImportableFunctionCallLinkInfo, entrypointLoadLocation); } static size_t offsetOfBoxedCallee(const Wasm::ModuleInformation& info, size_t importFunctionNum) { return offsetOfImportFunctionInfo(info, importFunctionNum) + OBJECT_OFFSETOF(Wasm::WasmOrJSImportableFunctionCallLinkInfo, boxedCallee); } @@ -420,6 +425,16 @@ class JSWebAssemblyInstance final : public JSNonFinalObject { return unsafeMakeSpan(std::bit_cast(std::bit_cast(this) + offsetOfAllocatorForGCObject(m_moduleInformation, 0)), MarkedSpace::numSizeClasses); } + std::span> functionWrappers() + { + return std::span { std::bit_cast*>(std::bit_cast(this) + offsetOfFunctionWrapper(m_moduleInformation, 0)), m_moduleInformation->functionIndexSpaceSize() }; + } + + std::span> functionWrappers() const + { + return std::span { std::bit_cast*>(std::bit_cast(this) + offsetOfFunctionWrapper(m_moduleInformation, 0)), m_moduleInformation->functionIndexSpaceSize() }; + } + unsigned numImportFunctions() const { return m_numImportFunctions; } WasmOrJSImportableFunctionCallLinkInfo* importFunctionInfo(size_t importFunctionNum) { @@ -485,7 +500,6 @@ class JSWebAssemblyInstance final : public JSNonFinalObject { RefPtr m_wasmMemory; Wasm::Global::Value* m_globals { nullptr }; - FunctionWrapperMap m_functionWrappers; using ConstantExpressionValueMap = UncheckedKeyHashMap, IntHash, WTF::UnsignedWithZeroKeyHashTraits>; ConstantExpressionValueMap m_constantExpressionValues; From d3e6e0f710a4f913cc4281834f9810ab07962797 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 09:35:32 -0700 Subject: [PATCH 047/103] [JSC][Wasm] Fast-path i31ref in the JS-to-Wasm entry stub https://bugs.webkit.org/show_bug.cgi?id=322525 Reviewed by Yusuke Suzuki. The JS-to-Wasm IC sent every non-externref to the slow path. Accept a JS int32 in the i31 range, and null if the type is nullable. * Source/JavaScriptCore/wasm/js/JSToWasm.cpp: * JSTests/wasm/stress/js-to-wasm-i31ref.js: Added. Canonical link: https://commits.webkit.org/320055@main --- JSTests/wasm/stress/js-to-wasm-i31ref.js | 36 ++++++++++++++++++++++ Source/JavaScriptCore/wasm/js/JSToWasm.cpp | 12 ++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 JSTests/wasm/stress/js-to-wasm-i31ref.js diff --git a/JSTests/wasm/stress/js-to-wasm-i31ref.js b/JSTests/wasm/stress/js-to-wasm-i31ref.js new file mode 100644 index 000000000000..c195d8040c91 --- /dev/null +++ b/JSTests/wasm/stress/js-to-wasm-i31ref.js @@ -0,0 +1,36 @@ +import { instantiate } from "../gc/wast-wrapper.js"; +import * as assert from "../assert.js"; + +let wat = ` +(module + (func (export "get") (param (ref i31)) (result i32) + (i31.get_s (local.get 0))) + (func (export "getNullable") (param i31ref) (result i32) + (if (result i32) + (ref.is_null (local.get 0)) + (then (i32.const -1)) + (else (i31.get_s (local.get 0))))) +) +`; + +async function test() { + const instance = instantiate(wat); + const { get, getNullable } = instance.exports; + + for (let i = 0; i < wasmTestLoopCount; i++) { + assert.eq(get(0), 0); + assert.eq(get(2), 2); + assert.eq(get(2 ** 30 - 1), 2 ** 30 - 1); + assert.eq(get(-(2 ** 30)), -(2 ** 30)); + assert.eq(getNullable(null), -1); + assert.eq(getNullable(7), 7); + } + + assert.throws(() => get(2.3), TypeError, "Argument value did not match the reference type"); + assert.throws(() => get(2n), TypeError, "Argument value did not match the reference type"); + assert.throws(() => get(2 ** 30), TypeError, "Argument value did not match the reference type"); + assert.throws(() => get(-(2 ** 30) - 1), TypeError, "Argument value did not match the reference type"); + assert.throws(() => get(null), TypeError, "Argument value did not match the reference type"); +} + +await assert.asyncTest(test()); diff --git a/Source/JavaScriptCore/wasm/js/JSToWasm.cpp b/Source/JavaScriptCore/wasm/js/JSToWasm.cpp index f1e7f0b10c65..8a020292f7e4 100644 --- a/Source/JavaScriptCore/wasm/js/JSToWasm.cpp +++ b/Source/JavaScriptCore/wasm/js/JSToWasm.cpp @@ -600,11 +600,19 @@ CodePtr RTT::jsToWasmICEntrypoint() const slowPath.append(jit.branchPtr(CCallHelpers::NotEqual, scratchJSR.payloadGPR(), CCallHelpers::TrustedImmPtr(targetRTT.ptr()))); } + if (type.isNullable()) + isNull.link(&jit); + } else if (Wasm::isI31ref(type)) { + jit.loadValue(jsParam, scratchJSR); + auto isNull = jit.branchIfNull(scratchJSR); + if (!type.isNullable()) + slowPath.append(isNull); + slowPath.append(jit.branchIfNotInt32(scratchJSR, DoNotHaveTagRegisters)); + slowPath.append(jit.branch32(CCallHelpers::GreaterThan, scratchJSR.payloadGPR(), CCallHelpers::TrustedImm32(Wasm::maxI31ref))); + slowPath.append(jit.branch32(CCallHelpers::LessThan, scratchJSR.payloadGPR(), CCallHelpers::TrustedImm32(Wasm::minI31ref))); if (type.isNullable()) isNull.link(&jit); } else if (!Wasm::isExternref(type)) { - // FIXME: this should implement some fast paths for, e.g., i31refs and other - // types that can be easily handled. slowPath.append(jit.jump()); } From 565aa489f1f01a6f05274e682d17d9198410fa99 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 09:45:53 -0700 Subject: [PATCH 048/103] [JSC][Wasm] Fill wasm ref arrays without per-element set() https://bugs.webkit.org/show_bug.cgi?id=322524 Reviewed by Keith Miller. Ref fill looped set() so each element took a write barrier. Store with setWithoutWriteBarrier, then one writeBarrier. * Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp: * Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h: * Source/JavaScriptCore/wasm/js/JSWebAssemblyArrayInlines.h: Canonical link: https://commits.webkit.org/320056@main --- Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp | 6 +++--- Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h | 1 + .../wasm/js/JSWebAssemblyArrayInlines.h | 11 ++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp index 47478b1a743e..f15b4205dade 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp @@ -69,9 +69,9 @@ void JSWebAssemblyArray::fill(VM& vm, uint32_t offset, uint64_t value, uint32_t { // Handle ref types separately to ensure write barriers are in effect. if (elementsAreRefTypes()) { - // FIXME: We should have a GCSafeMemfill. - for (size_t i = 0; i < size; i++) - set(vm, offset + i, value); + for (size_t i = 0; i < size; ++i) + setWithoutWriteBarrier(offset + i, value); + vm.writeBarrier(this); return; } diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h index 172f8056b516..0e20af09f7ae 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h @@ -132,6 +132,7 @@ class JSWebAssemblyArray final : public WebAssemblyGCObjectBase { private: friend class LLIntOffsetsExtractor; + inline void setWithoutWriteBarrier(uint32_t index, uint64_t value); inline std::span bytes(); // NB: It's *HIGHLY* recommended that you don't use these directly since you'll have to remember to clean up the alignment for v128. diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArrayInlines.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArrayInlines.h index bc50a2469ba8..27e06128f567 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArrayInlines.h +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArrayInlines.h @@ -142,15 +142,20 @@ v128_t JSWebAssemblyArray::getVector(uint32_t index) return span()[index]; } -void JSWebAssemblyArray::set(VM& vm, uint32_t index, uint64_t value) +void JSWebAssemblyArray::setWithoutWriteBarrier(uint32_t index, uint64_t value) { visitSpanNonVector([&](std::span span) ALWAYS_INLINE_LAMBDA { span[index] = static_cast(value); - if (elementsAreRefTypes()) - vm.writeBarrier(this); }); } +void JSWebAssemblyArray::set(VM& vm, uint32_t index, uint64_t value) +{ + setWithoutWriteBarrier(index, value); + if (elementsAreRefTypes()) + vm.writeBarrier(this); +} + void JSWebAssemblyArray::set(VM&, uint32_t index, v128_t value) { ASSERT(elementType().type.as().kind() == Wasm::TypeKind::V128); From bb8cb62b7160699e476f4fd7c5de0acebc037a65 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 09:49:02 -0700 Subject: [PATCH 049/103] [JSC][Wasm] Expect CompileError for an empty WebAssembly.Module Reviewed by Yusuke Suzuki. Empty buffers already throw WebAssembly.CompileError. Drop the stale FIXME that expected Error (the superclass). * JSTests/wasm/js-api/test_basic_api.js: Canonical link: https://commits.webkit.org/320057@main --- JSTests/wasm/js-api/test_basic_api.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/JSTests/wasm/js-api/test_basic_api.js b/JSTests/wasm/js-api/test_basic_api.js index 1231915b9554..fa4f19788da6 100644 --- a/JSTests/wasm/js-api/test_basic_api.js +++ b/JSTests/wasm/js-api/test_basic_api.js @@ -66,8 +66,7 @@ for (const c in constructorProperties) { for (const invalid of invalidConstructorInputs) assert.throws(() => new WebAssembly[c](invalid), TypeError, `first argument must be an ArrayBufferView or an ArrayBuffer (evaluating 'new WebAssembly[c](invalid)')`); for (const buffer of [new ArrayBuffer(), new DataView(new ArrayBuffer()), new Int8Array(), new Uint8Array(), new Uint8ClampedArray(), new Int16Array(), new Uint16Array(), new Int32Array(), new Uint32Array(), new Float32Array(), new Float64Array()]) - // FIXME the following should be WebAssembly.CompileError. https://bugs.webkit.org/show_bug.cgi?id=163768 - assert.throws(() => new WebAssembly[c](buffer), Error, `WebAssembly.Module doesn't parse at byte 0: expected a module of at least 8 bytes (evaluating 'new WebAssembly[c](buffer)')`); + assert.throws(() => new WebAssembly[c](buffer), WebAssembly.CompileError, `WebAssembly.Module doesn't parse at byte 0: expected a module of at least 8 bytes (evaluating 'new WebAssembly[c](buffer)')`); assert.instanceof(new WebAssembly[c](emptyModuleArray), WebAssembly.Module); break; case "Instance": From 81b5126daf2dac78574dc7c15bf64d88469ee206 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 09:52:36 -0700 Subject: [PATCH 050/103] [JSC][Wasm] ESM mutable globals should be live unwrapped bindings https://bugs.webkit.org/show_bug.cgi?id=322116 Reviewed by Yusuke Suzuki. Namespace get of a mutable Wasm global returns the current value instead of the WebAssembly.Global wrapper. Instance.exports stays wrapped. v128 / exnref stay TDZ. Module-namespace IC is not installed for these gets so the value stays live. A JS module that exports a mutable WebAssembly.Global is unchanged. * Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp: * JSTests/wasm/modules/js-wasm-mutable-global-namespace.js: Added. * JSTests/wasm/modules/mut-global.wasm: Added. * JSTests/wasm/modules/mut-global.wat: Added. * JSTests/wasm/js-api/js-module-mutable-global-export.js: Added. * JSTests/wasm/js-api/js-module-mutable-global-namespace.js: Added. * JSTests/wasm/js-api/js-wasm-imported-mut-global-namespace.js: Added. * JSTests/wasm/js-api/reexport-js-mut-global.wasm: Added. * JSTests/wasm/js-api/reexport-js-mut-global.wat: Added. * JSTests/wasm/js-api/js-reexport-wasm-mut-global.js: Added. * JSTests/wasm/js-api/js-reexport-wasm-mut-global-namespace.js: Added. * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/global-exports.tentative.any-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/global-exports.tentative.any.worker-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/global-exports-live-bindings.tentative.any-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/global-exports-live-bindings.tentative.any.worker-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.worker-expected.txt: Canonical link: https://commits.webkit.org/320058@main --- .../js-api/js-module-mutable-global-export.js | 1 + .../js-api/js-module-mutable-global-namespace.js | 8 ++++++++ .../js-reexport-wasm-mut-global-namespace.js | 7 +++++++ .../wasm/js-api/js-reexport-wasm-mut-global.js | 1 + .../js-wasm-imported-mut-global-namespace.js | 11 +++++++++++ JSTests/wasm/js-api/reexport-js-mut-global.wasm | Bin 0 -> 60 bytes JSTests/wasm/js-api/reexport-js-mut-global.wat | 3 +++ .../modules/js-wasm-mutable-global-namespace.js | 13 +++++++++++++ JSTests/wasm/modules/mut-global.wasm | Bin 0 -> 67 bytes JSTests/wasm/modules/mut-global.wat | 7 +++++++ ...orts-live-bindings.tentative.any-expected.txt | 4 ++-- ...ve-bindings.tentative.any.worker-expected.txt | 4 ++-- .../global-exports.tentative.any-expected.txt | 8 ++++---- ...bal-exports.tentative.any.worker-expected.txt | 8 ++++---- ...ble-global-sharing.tentative.any-expected.txt | 2 +- ...bal-sharing.tentative.any.worker-expected.txt | 2 +- .../runtime/JSModuleNamespaceObject.cpp | 15 +++++++++++++++ 17 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 JSTests/wasm/js-api/js-module-mutable-global-export.js create mode 100644 JSTests/wasm/js-api/js-module-mutable-global-namespace.js create mode 100644 JSTests/wasm/js-api/js-reexport-wasm-mut-global-namespace.js create mode 100644 JSTests/wasm/js-api/js-reexport-wasm-mut-global.js create mode 100644 JSTests/wasm/js-api/js-wasm-imported-mut-global-namespace.js create mode 100644 JSTests/wasm/js-api/reexport-js-mut-global.wasm create mode 100644 JSTests/wasm/js-api/reexport-js-mut-global.wat create mode 100644 JSTests/wasm/modules/js-wasm-mutable-global-namespace.js create mode 100644 JSTests/wasm/modules/mut-global.wasm create mode 100644 JSTests/wasm/modules/mut-global.wat diff --git a/JSTests/wasm/js-api/js-module-mutable-global-export.js b/JSTests/wasm/js-api/js-module-mutable-global-export.js new file mode 100644 index 000000000000..f7930af3a1f6 --- /dev/null +++ b/JSTests/wasm/js-api/js-module-mutable-global-export.js @@ -0,0 +1 @@ +export const g = new WebAssembly.Global({ value: "i32", mutable: true }, 1); diff --git a/JSTests/wasm/js-api/js-module-mutable-global-namespace.js b/JSTests/wasm/js-api/js-module-mutable-global-namespace.js new file mode 100644 index 000000000000..c1c3fb61368f --- /dev/null +++ b/JSTests/wasm/js-api/js-module-mutable-global-namespace.js @@ -0,0 +1,8 @@ +import * as ns from "./js-module-mutable-global-export.js" +import * as assert from "../assert.js"; + +assert.instanceof(ns.g, WebAssembly.Global); +assert.eq(ns.g.value, 1); +ns.g.value = 7; +assert.eq(ns.g.value, 7); +assert.instanceof(ns.g, WebAssembly.Global); diff --git a/JSTests/wasm/js-api/js-reexport-wasm-mut-global-namespace.js b/JSTests/wasm/js-api/js-reexport-wasm-mut-global-namespace.js new file mode 100644 index 000000000000..536b75e55b23 --- /dev/null +++ b/JSTests/wasm/js-api/js-reexport-wasm-mut-global-namespace.js @@ -0,0 +1,7 @@ +import * as ns from "./js-reexport-wasm-mut-global.js" +import * as assert from "../assert.js"; + +assert.eq(ns.g, 100); +ns.set(3); +assert.eq(ns.get(), 3); +assert.eq(ns.g, 3); diff --git a/JSTests/wasm/js-api/js-reexport-wasm-mut-global.js b/JSTests/wasm/js-api/js-reexport-wasm-mut-global.js new file mode 100644 index 000000000000..359daa4f59d2 --- /dev/null +++ b/JSTests/wasm/js-api/js-reexport-wasm-mut-global.js @@ -0,0 +1 @@ +export { g, get, set } from "../modules/mut-global.wasm"; diff --git a/JSTests/wasm/js-api/js-wasm-imported-mut-global-namespace.js b/JSTests/wasm/js-api/js-wasm-imported-mut-global-namespace.js new file mode 100644 index 000000000000..4ef6065f266f --- /dev/null +++ b/JSTests/wasm/js-api/js-wasm-imported-mut-global-namespace.js @@ -0,0 +1,11 @@ +import * as ns from "./reexport-js-mut-global.wasm" +import { g } from "./js-module-mutable-global-export.js" +import * as assert from "../assert.js"; + +assert.eq(ns.g, 1); +assert.instanceof(g, WebAssembly.Global); +assert.eq(g.value, 1); + +g.value = 9; +assert.eq(ns.g, 9); +assert.eq(g.value, 9); diff --git a/JSTests/wasm/js-api/reexport-js-mut-global.wasm b/JSTests/wasm/js-api/reexport-js-mut-global.wasm new file mode 100644 index 0000000000000000000000000000000000000000..cbfac2fd3b1a705bdee9bab0ae7b5cbcef1ca0b3 GIT binary patch literal 60 zcmZQbEY4+QU|?XnGB&nni<%}*)KN!86QElErQ(&;(*Nr^eSsTBqJMJ0M!#f<69 M^^EMSj6j+J0QZCtbN~PV literal 0 HcmV?d00001 diff --git a/JSTests/wasm/js-api/reexport-js-mut-global.wat b/JSTests/wasm/js-api/reexport-js-mut-global.wat new file mode 100644 index 000000000000..0e8b69023c51 --- /dev/null +++ b/JSTests/wasm/js-api/reexport-js-mut-global.wat @@ -0,0 +1,3 @@ +(module + (import "./js-module-mutable-global-export.js" "g" (global $g (mut i32))) + (export "g" (global $g))) diff --git a/JSTests/wasm/modules/js-wasm-mutable-global-namespace.js b/JSTests/wasm/modules/js-wasm-mutable-global-namespace.js new file mode 100644 index 000000000000..8b2ce01950c6 --- /dev/null +++ b/JSTests/wasm/modules/js-wasm-mutable-global-namespace.js @@ -0,0 +1,13 @@ +import * as ns from "./mut-global.wasm" +import * as assert from '../assert.js'; + +assert.eq(ns.g, 100); +assert.eq(ns.get(), 100); + +ns.set(555); +assert.eq(ns.get(), 555); +assert.eq(ns.g, 555); + +assert.throws(() => { + ns.g = 1; +}, TypeError, `Attempted to assign to readonly property.`); diff --git a/JSTests/wasm/modules/mut-global.wasm b/JSTests/wasm/modules/mut-global.wasm new file mode 100644 index 0000000000000000000000000000000000000000..09baba67d34d68b3a6efae627a4341f951028c62 GIT binary patch literal 67 zcmWN>u?>JQ3`N2BW0VLi3N}E3Mc5=1C>2%cy)1*k7020c0UA?PP*IpAl#4>gi(exportEntry.moduleRecord.get())) { + if (auto* wasmGlobal = dynamicDowncast(value); wasmGlobal && wasmGlobal->global()->mutability() == Wasm::Mutability::Mutable) { + value = wasmGlobal->global()->get(globalObject); + RETURN_IF_EXCEPTION(scope, false); + slot.setValue(this, static_cast(PropertyAttribute::DontDelete), value); + return true; + } + } +#endif + slot.setValueModuleNamespace(this, static_cast(PropertyAttribute::DontDelete), value, environment, scopeOffset); return true; } From 6f7bb45088c88c051ca99c0c0afeb5baf9364f16 Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 09:57:53 -0700 Subject: [PATCH 051/103] [JSC][Wasm] Reserved wasm: and wasm-js: names should LinkError in ESM https://bugs.webkit.org/show_bug.cgi?id=322118 Reviewed by Keith Miller. ESM import of a Wasm module should reject reserved wasm: and wasm-js: import names, export names, and wasm-js: module names with WebAssembly.LinkError instead of TypeError or a successful load. WebAssembly.Instance is unchanged. Enabled wasm: builtins are not treated as reserved. * Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp: * JSTests/wasm/modules/js-wasm-reserved-names.js: Added. * JSTests/wasm/modules/reserved-import-name.wat: Added. * JSTests/wasm/modules/reserved-import-name.wasm: Added. * JSTests/wasm/modules/reserved-import-name-wasm-js.wat: Added. * JSTests/wasm/modules/reserved-import-name-wasm-js.wasm: Added. * JSTests/wasm/modules/reserved-export-name.wat: Added. * JSTests/wasm/modules/reserved-export-name.wasm: Added. * JSTests/wasm/modules/reserved-export-name-wasm-js.wat: Added. * JSTests/wasm/modules/reserved-export-name-wasm-js.wasm: Added. * JSTests/wasm/modules/reserved-import-module.wat: Added. * JSTests/wasm/modules/reserved-import-module.wasm: Added. * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/reserved-import-names.tentative.any-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/esm-integration/reserved-import-names.tentative.any.worker-expected.txt: Canonical link: https://commits.webkit.org/320059@main --- .../wasm/modules/js-wasm-reserved-names.js | 21 ++++++++++++++++++ .../modules/reserved-export-name-wasm-js.wasm | Bin 0 -> 48 bytes .../modules/reserved-export-name-wasm-js.wat | 3 +++ .../wasm/modules/reserved-export-name.wasm | Bin 0 -> 45 bytes JSTests/wasm/modules/reserved-export-name.wat | 3 +++ .../wasm/modules/reserved-import-module.wasm | Bin 0 -> 63 bytes .../wasm/modules/reserved-import-module.wat | 4 ++++ .../modules/reserved-import-name-wasm-js.wasm | Bin 0 -> 63 bytes .../modules/reserved-import-name-wasm-js.wat | 4 ++++ .../wasm/modules/reserved-import-name.wasm | Bin 0 -> 60 bytes JSTests/wasm/modules/reserved-import-name.wat | 4 ++++ JSTests/wasm/modules/wasm-colon-module.wasm | Bin 0 -> 40 bytes JSTests/wasm/modules/wasm-colon-module.wat | 2 ++ ...ed-import-names.tentative.any-expected.txt | 16 +++++-------- ...rt-names.tentative.any.worker-expected.txt | 16 +++++-------- .../wasm/js/JSWebAssemblyInstance.cpp | 21 +++++++++++++++++- 16 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 JSTests/wasm/modules/js-wasm-reserved-names.js create mode 100644 JSTests/wasm/modules/reserved-export-name-wasm-js.wasm create mode 100644 JSTests/wasm/modules/reserved-export-name-wasm-js.wat create mode 100644 JSTests/wasm/modules/reserved-export-name.wasm create mode 100644 JSTests/wasm/modules/reserved-export-name.wat create mode 100644 JSTests/wasm/modules/reserved-import-module.wasm create mode 100644 JSTests/wasm/modules/reserved-import-module.wat create mode 100644 JSTests/wasm/modules/reserved-import-name-wasm-js.wasm create mode 100644 JSTests/wasm/modules/reserved-import-name-wasm-js.wat create mode 100644 JSTests/wasm/modules/reserved-import-name.wasm create mode 100644 JSTests/wasm/modules/reserved-import-name.wat create mode 100644 JSTests/wasm/modules/wasm-colon-module.wasm create mode 100644 JSTests/wasm/modules/wasm-colon-module.wat diff --git a/JSTests/wasm/modules/js-wasm-reserved-names.js b/JSTests/wasm/modules/js-wasm-reserved-names.js new file mode 100644 index 000000000000..283c865026c4 --- /dev/null +++ b/JSTests/wasm/modules/js-wasm-reserved-names.js @@ -0,0 +1,21 @@ +import * as assert from '../assert.js'; + +function assertLinkError(promise) { + return promise.then($vm.abort, function (error) { + assert.eq(error instanceof WebAssembly.LinkError, true); + }); +} + +assertLinkError(import("./reserved-import-name.wasm")) + .then(() => assertLinkError(import("./reserved-import-name-wasm-js.wasm"))) + .then(() => assertLinkError(import("./reserved-export-name.wasm"))) + .then(() => assertLinkError(import("./reserved-export-name-wasm-js.wasm"))) + .then(() => assertLinkError(import("./reserved-import-module.wasm"))) + .then(() => import("./wasm-colon-module.wasm").then($vm.abort, function (error) { + assert.eq(error instanceof WebAssembly.LinkError && String(error).includes("is reserved"), false); + })) + .then(function () { }, $vm.abort); + +const { "wasm:invalid": fn } = new WebAssembly.Instance(new WebAssembly.Module(read("./reserved-export-name.wasm", "binary"))).exports; +assert.isFunction(fn); +assert.eq(fn(), 42); diff --git a/JSTests/wasm/modules/reserved-export-name-wasm-js.wasm b/JSTests/wasm/modules/reserved-export-name-wasm-js.wasm new file mode 100644 index 0000000000000000000000000000000000000000..363562c7f74770fc1b3fca4f2304fa3346fedfab GIT binary patch literal 48 zcmZQbEY4+QU|?WmWlUgTtY>CoWMCI&t+>OW#*M7=47TYFmSOkvM@MmaRUI# Ctq8~f literal 0 HcmV?d00001 diff --git a/JSTests/wasm/modules/reserved-export-name-wasm-js.wat b/JSTests/wasm/modules/reserved-export-name-wasm-js.wat new file mode 100644 index 000000000000..d79575339e22 --- /dev/null +++ b/JSTests/wasm/modules/reserved-export-name-wasm-js.wat @@ -0,0 +1,3 @@ +(module + (func (export "wasm-js:invalid") (result i32) + i32.const 42)) diff --git a/JSTests/wasm/modules/reserved-export-name.wasm b/JSTests/wasm/modules/reserved-export-name.wasm new file mode 100644 index 0000000000000000000000000000000000000000..cd8be87943b557a9d75056ae13390d272c26f233 GIT binary patch literal 45 zcmZQbEY4+QU|?WmWlUgTtY>CoWMCIy{zCFW$NFfeejF|sf?YH1GvMW#*M7=47U@l%y7yFfcGPF*2}oFhY2Y MTx^Ui3<3{zCFW$NFfcGPF*2}oKx7#h Mx!4$47z7x&0il8n1^@s6 literal 0 HcmV?d00001 diff --git a/JSTests/wasm/modules/reserved-import-name-wasm-js.wat b/JSTests/wasm/modules/reserved-import-name-wasm-js.wat new file mode 100644 index 000000000000..3fe4fe4247da --- /dev/null +++ b/JSTests/wasm/modules/reserved-import-name-wasm-js.wat @@ -0,0 +1,4 @@ +(module + (import "test" "wasm-js:invalid" (func $invalid (result i32))) + (func (export "test") (result i32) + call $invalid)) diff --git a/JSTests/wasm/modules/reserved-import-name.wasm b/JSTests/wasm/modules/reserved-import-name.wasm new file mode 100644 index 0000000000000000000000000000000000000000..79020e8d61441dbab09a0a6b5f019e5886b6463d GIT binary patch literal 60 zcmZQbEY4+QU|?WmWlUgTtY;EsWGP84F5xK$id$vol_ln6rZ6xtGchu-b3mjR7`fOO JSr`NuxB+Yt3ibd1 literal 0 HcmV?d00001 diff --git a/JSTests/wasm/modules/reserved-import-name.wat b/JSTests/wasm/modules/reserved-import-name.wat new file mode 100644 index 000000000000..53a6d4d478f1 --- /dev/null +++ b/JSTests/wasm/modules/reserved-import-name.wat @@ -0,0 +1,4 @@ +(module + (import "test" "wasm:invalid" (func $invalid (result i32))) + (func (export "test") (result i32) + call $invalid)) diff --git a/JSTests/wasm/modules/wasm-colon-module.wasm b/JSTests/wasm/modules/wasm-colon-module.wasm new file mode 100644 index 0000000000000000000000000000000000000000..37b63c127793520323da604dfd119d2934c957e4 GIT binary patch literal 40 ucmZQbEY4+QU|?WmVN76PV3J@IDhG;K<>i;?Ch8`YX6BS+<}p?appendRequestedModule(moduleName, nullptr); moduleRecord->addImportEntry(WebAssemblyModuleRecord::ImportEntry { WebAssemblyModuleRecord::ImportEntryType::Single, From 209e271d944ef2a3758bc25ca8ff8ab9bd149155 Mon Sep 17 00:00:00 2001 From: Ben Nham Date: Fri, 28 Aug 2026 10:21:28 -0700 Subject: [PATCH 052/103] Move some part of collectScreenProperties to a background thread https://bugs.webkit.org/show_bug.cgi?id=322756 rdar://184130213 Reviewed by Simon Fraser. We are getting hang logs showing that some part of collectScreenProperties (the call to `+preferredVideoRangeForDisplays:`) ends up in a sync IPC to WindowServer that can sometimes block the main thread of the UIProcess for multiple seconds. To fix this, this patch adds a collectScreenPropertiesAsync function, which moves just the HDR state collection for a display to a background thread. (Note that I considered just running all of collectScreenProperties on a background thread, but after inspection it seems like some NSScreen methods aren't meant to be called off the main thread.) In addition, WebProcessPool now caches the last screen properties state and uses it on the WebProcess creation path. This removes a sync IPC from the WebProcess creation path. Test: Tools/TestWebKitAPI/Tests/WebCore/cocoa/PlatformScreenTests.mm * Source/WebCore/platform/PlatformScreen.cpp: (WebCore::collectScreenPropertiesAsync): * Source/WebCore/platform/PlatformScreen.h: * Source/WebCore/platform/mac/PlatformScreenMac.mm: (WebCore::collectHDRStateForDisplay): (WebCore::collectHDRState): (WebCore::collectScreenPropertiesExceptForHDRState): (WebCore::collectScreenProperties): (WebCore::collectScreenPropertiesAsync): * Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm: (WebKit::WebProcessPool::platformInitializeWebProcess): (WebKit::WebProcessPool::screenPropertiesUpdateTimerFired): (WebKit::WebProcessPool::didCollectScreenProperties): (WebKit::WebProcessPool::applyEDRSuppressionIfNeeded): (WebKit::WebProcessPool::cachedScreenProperties): * Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp: (WebKit::GPUProcessProxy::updateScreenPropertiesIfNeeded): * Source/WebKit/UIProcess/GPU/GPUProcessProxy.h: * Source/WebKit/UIProcess/WebProcessPool.cpp: (WebKit::WebProcessPool::ensureGPUProcess): (WebKit::WebProcessPool::createWebPage): * Source/WebKit/UIProcess/WebProcessPool.h: * Tools/TestWebKitAPI/PlatformCocoa.cmake: * Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: * Tools/TestWebKitAPI/Tests/WebCore/cocoa/PlatformScreenTests.mm: Added. (TestWebKitAPI::TEST(PlatformScreen, CollectScreenPropertiesAsync)): Canonical link: https://commits.webkit.org/320060@main --- Source/WebCore/platform/PlatformScreen.cpp | 14 ++++ Source/WebCore/platform/PlatformScreen.h | 2 + .../WebCore/platform/mac/PlatformScreenMac.mm | 81 ++++++++++++++----- .../UIProcess/Cocoa/WebProcessPoolCocoa.mm | 71 +++++++++++++--- .../WebKit/UIProcess/GPU/GPUProcessProxy.cpp | 6 +- Source/WebKit/UIProcess/GPU/GPUProcessProxy.h | 3 +- Source/WebKit/UIProcess/WebProcessPool.cpp | 4 +- Source/WebKit/UIProcess/WebProcessPool.h | 16 ++++ Tools/TestWebKitAPI/PlatformCocoa.cmake | 1 + .../TestWebKitAPI.xcodeproj/project.pbxproj | 1 + .../WebCore/cocoa/PlatformScreenTests.mm | 73 +++++++++++++++++ 11 files changed, 234 insertions(+), 38 deletions(-) create mode 100644 Tools/TestWebKitAPI/Tests/WebCore/cocoa/PlatformScreenTests.mm diff --git a/Source/WebCore/platform/PlatformScreen.cpp b/Source/WebCore/platform/PlatformScreen.cpp index 154251118cd4..3f7835df0d13 100644 --- a/Source/WebCore/platform/PlatformScreen.cpp +++ b/Source/WebCore/platform/PlatformScreen.cpp @@ -30,6 +30,7 @@ #include "ScreenProperties.h" #include +#include #include #include @@ -124,6 +125,19 @@ void PlatformScreen::updateSingletonContentsFormatsForTesting(OptionSet&& completionHandler) +{ + ASSERT(isMainThread()); + + callOnMainThread([screenProperties = collectScreenProperties(), completionHandler = WTF::move(completionHandler)]() mutable { + completionHandler(WTF::move(screenProperties)); + }); +} + +#endif // PLATFORM(COCOA) && !PLATFORM(MAC) + } // namespace WebCore #endif // PLATFORM(COCOA) || PLATFORM(GTK) || (PLATFORM(WPE) && ENABLE(WPE_PLATFORM)) diff --git a/Source/WebCore/platform/PlatformScreen.h b/Source/WebCore/platform/PlatformScreen.h index 68f7a5502c84..ea0c97d040f3 100644 --- a/Source/WebCore/platform/PlatformScreen.h +++ b/Source/WebCore/platform/PlatformScreen.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -121,6 +122,7 @@ class PlatformScreen : public ThreadSafeRefCounted { }; WEBCORE_EXPORT ScreenProperties collectScreenProperties(); +WEBCORE_EXPORT void collectScreenPropertiesAsync(CompletionHandler&&); #if HAVE(SUPPORT_HDR_DISPLAY) WEBCORE_EXPORT float currentEDRHeadroomForDisplay(PlatformDisplayID); diff --git a/Source/WebCore/platform/mac/PlatformScreenMac.mm b/Source/WebCore/platform/mac/PlatformScreenMac.mm index 93c4db7228e4..62e3c8a4bea8 100644 --- a/Source/WebCore/platform/mac/PlatformScreenMac.mm +++ b/Source/WebCore/platform/mac/PlatformScreenMac.mm @@ -39,7 +39,10 @@ #import #import #import +#import +#import #import +#import #import @@ -129,37 +132,51 @@ static DynamicRangeMode convertAVVideoRangeToEnum(NSString* range) } #endif -ScreenProperties collectScreenProperties() +// May run on the main thread or a background thread. +static bool collectHDRStateForDisplay(PlatformDisplayID displayID, DynamicRangeMode& dynamicRangeMode) { - ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); - - ScreenProperties screenProperties; - bool screenHasInvertedColors = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldInvertColors]; - - auto screenSupportsHighDynamicRange = [](PlatformDisplayID displayID, DynamicRangeMode& dynamicRangeMode) { - bool supportsHighDynamicRange = false; + bool supportsHighDynamicRange = false; #if HAVE(AVPLAYER_VIDEORANGEOVERRIDE) - if (PAL::isAVFoundationFrameworkAvailable()) { - dynamicRangeMode = convertAVVideoRangeToEnum([PAL::getAVPlayerClassSingleton() preferredVideoRangeForDisplays:@[ @(displayID) ]]); - supportsHighDynamicRange = dynamicRangeMode > DynamicRangeMode::Standard; - } + if (PAL::isAVFoundationFrameworkAvailable()) { + dynamicRangeMode = convertAVVideoRangeToEnum([PAL::getAVPlayerClassSingleton() preferredVideoRangeForDisplays:@[ @(displayID) ]]); + supportsHighDynamicRange = dynamicRangeMode > DynamicRangeMode::Standard; + } #endif #if HAVE(AVPLAYER_VIDEORANGEOVERRIDE) && USE(MEDIATOOLBOX) - else + else #endif #if USE(MEDIATOOLBOX) - if (PAL::isMediaToolboxFrameworkAvailable() && PAL::canLoad_MediaToolbox_MTShouldPlayHDRVideo()) - supportsHighDynamicRange = PAL::softLink_MediaToolbox_MTShouldPlayHDRVideo((__bridge CFArrayRef)@[ @(displayID) ]); + if (PAL::isMediaToolboxFrameworkAvailable() && PAL::canLoad_MediaToolbox_MTShouldPlayHDRVideo()) + supportsHighDynamicRange = PAL::softLink_MediaToolbox_MTShouldPlayHDRVideo((__bridge CFArrayRef)@[ @(displayID) ]); #endif - if (!supportsHighDynamicRange && dynamicRangeMode > DynamicRangeMode::Standard) - dynamicRangeMode = DynamicRangeMode::Standard; + if (!supportsHighDynamicRange && dynamicRangeMode > DynamicRangeMode::Standard) + dynamicRangeMode = DynamicRangeMode::Standard; + + if (supportsHighDynamicRange && WebCore::ThermalMitigationNotifier::isThermalMitigationEnabled()) + supportsHighDynamicRange = false; + + return supportsHighDynamicRange; +} + +static void collectHDRState(ScreenProperties& screenProperties) +{ + for (auto& [displayID, screenData] : screenProperties.screenDataMap) + screenData.screenSupportsHighDynamicRange = collectHDRStateForDisplay(displayID, screenData.preferredDynamicRangeMode); +} + +static WorkQueue& screenPropertiesQueueSingleton() +{ + static NeverDestroyed> queue(WorkQueue::create("org.webkit.ScreenProperties"_s, WorkQueue::QOS::UserInitiated)); + return queue.get(); +} - if (supportsHighDynamicRange && WebCore::ThermalMitigationNotifier::isThermalMitigationEnabled()) - supportsHighDynamicRange = false; +static ScreenProperties collectScreenPropertiesExceptForHDRState() +{ + ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); - return supportsHighDynamicRange; - }; + ScreenProperties screenProperties; + bool screenHasInvertedColors = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldInvertColors]; for (NSScreen *screen in [NSScreen screens]) { ScreenData screenData; @@ -182,7 +199,6 @@ ScreenProperties collectScreenProperties() screenData.screenSize = FloatSize { CGDisplayScreenSize(displayID) }; screenData.scaleFactor = screen.backingScaleFactor; - screenData.screenSupportsHighDynamicRange = screenSupportsHighDynamicRange(displayID, screenData.preferredDynamicRangeMode); #if HAVE(SUPPORT_HDR_DISPLAY) screenData.maxEDRHeadroom = [screen maximumPotentialExtendedDynamicRangeColorComponentValue]; screenData.currentEDRHeadroom = [screen maximumExtendedDynamicRangeColorComponentValue]; @@ -198,6 +214,27 @@ ScreenProperties collectScreenProperties() return screenProperties; } +ScreenProperties collectScreenProperties() +{ + auto screenProperties = collectScreenPropertiesExceptForHDRState(); + collectHDRState(screenProperties); + return screenProperties; +} + +void collectScreenPropertiesAsync(CompletionHandler&& completionHandler) +{ + ASSERT(isMainThread()); + + auto screenProperties = collectScreenPropertiesExceptForHDRState(); + + screenPropertiesQueueSingleton().dispatch([screenProperties = WTF::move(screenProperties), completionHandler = WTF::move(completionHandler)]() mutable { + collectHDRState(screenProperties); + callOnMainThread([screenProperties = WTF::move(screenProperties), completionHandler = WTF::move(completionHandler)]() mutable { + completionHandler(WTF::move(screenProperties)); + }); + }); +} + void setShouldOverrideScreenSupportsHighDynamicRange(bool shouldOverride, bool supportsHighDynamicRange) { if (PAL::isMediaToolboxFrameworkAvailable() && PAL::canLoad_MediaToolbox_MTOverrideShouldPlayHDRVideo()) diff --git a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm index 075ac7291d49..dc6b3f01eef0 100644 --- a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm +++ b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm @@ -459,10 +459,11 @@ static void logProcessPoolState(const WebProcessPool& pool) parameters.shouldLogUserInteraction = [defaults boolForKey:WebKitLogCookieInformationDefaultsKey]; #endif - auto screenProperties = WebCore::collectScreenProperties(); - parameters.screenProperties = WTF::move(screenProperties); #if PLATFORM(MAC) + parameters.screenProperties = cachedScreenProperties(); parameters.useOverlayScrollbars = ([NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay); +#else + parameters.screenProperties = WebCore::collectScreenProperties(); #endif #if PLATFORM(VISION) @@ -1302,16 +1303,32 @@ void setLockdownModeEnabledGloballyForTesting(std::optional enabledForTest { m_lastScreenPropertiesUpdateTime = ApproximateTime::now(); - auto screenProperties = WebCore::collectScreenProperties(); -#if HAVE(SUPPORT_HDR_DISPLAY) - if (m_suppressEDR) { - for (auto& properties : screenProperties.screenDataMap.values()) { - constexpr auto maxSuppressedHeadroom = 1.6f; - auto suppressedHeadroom = std::min(maxSuppressedHeadroom, properties.currentEDRHeadroom); - properties.currentEDRHeadroom = suppressedHeadroom; - properties.suppressEDR = true; - } + if (m_screenPropertiesState != ScreenPropertiesState::Idle) { + m_screenPropertiesState = ScreenPropertiesState::CollectingWithUpdatePending; + return; } + + m_screenPropertiesState = ScreenPropertiesState::Collecting; + WebCore::collectScreenPropertiesAsync([weakThis = WeakPtr { *this }](WebCore::ScreenProperties&& screenProperties) { + if (RefPtr protectedThis = weakThis.get()) + protectedThis->didCollectScreenProperties(WTF::move(screenProperties)); + }); +} + +void WebProcessPool::didCollectScreenProperties(WebCore::ScreenProperties&& screenProperties) +{ + ASSERT(m_screenPropertiesState != ScreenPropertiesState::Idle); + + // If we got a screen change notification while collecting screen properties, then we need to + // schedule another screen properties update to reflect the most recent state. + bool needsUpdate = m_screenPropertiesState == ScreenPropertiesState::CollectingWithUpdatePending; + + m_screenPropertiesState = ScreenPropertiesState::Idle; + + applyEDRSuppressionIfNeeded(screenProperties); + +#if PLATFORM(MAC) + m_cachedScreenProperties = screenProperties; #endif sendToAllProcesses(Messages::WebProcess::SetScreenProperties(screenProperties)); @@ -1320,7 +1337,39 @@ void setLockdownModeEnabledGloballyForTesting(std::optional enabledForTest if (RefPtr gpuProcess = this->gpuProcess()) gpuProcess->setScreenProperties(screenProperties); #endif + + if (needsUpdate) + screenPropertiesChanged(); +} + +void WebProcessPool::applyEDRSuppressionIfNeeded(WebCore::ScreenProperties& screenProperties) +{ +#if HAVE(SUPPORT_HDR_DISPLAY) + if (!m_suppressEDR) + return; + + for (auto& properties : screenProperties.screenDataMap.values()) { + constexpr auto maxSuppressedHeadroom = 1.6f; + properties.currentEDRHeadroom = std::min(maxSuppressedHeadroom, properties.currentEDRHeadroom); + properties.suppressEDR = true; + } +#else + UNUSED_PARAM(screenProperties); +#endif +} + +#if PLATFORM(MAC) +const WebCore::ScreenProperties& WebProcessPool::cachedScreenProperties() +{ + if (!m_cachedScreenProperties) { + auto screenProperties = WebCore::collectScreenProperties(); + applyEDRSuppressionIfNeeded(screenProperties); + m_cachedScreenProperties = WTF::move(screenProperties); + } + + return *m_cachedScreenProperties; } +#endif void WebProcessPool::screenPropertiesChanged() { diff --git a/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp b/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp index a2038d17fe80..802b47eb419f 100644 --- a/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp +++ b/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp @@ -827,13 +827,15 @@ void GPUProcessProxy::updatePreferences(WebProcessProxy& webProcess) send(Messages::GPUProcess::UpdateGPUProcessPreferences(gpuPreferences), 0); } -void GPUProcessProxy::updateScreenPropertiesIfNeeded() +void GPUProcessProxy::updateScreenPropertiesIfNeeded(WebProcessPool& processPool) { #if PLATFORM(MAC) if (!canSendMessage()) return; - setScreenProperties(collectScreenProperties()); + setScreenProperties(processPool.cachedScreenProperties()); +#else + UNUSED_PARAM(processPool); #endif } diff --git a/Source/WebKit/UIProcess/GPU/GPUProcessProxy.h b/Source/WebKit/UIProcess/GPU/GPUProcessProxy.h index 962632397c45..a53b1f8919ac 100644 --- a/Source/WebKit/UIProcess/GPU/GPUProcessProxy.h +++ b/Source/WebKit/UIProcess/GPU/GPUProcessProxy.h @@ -73,6 +73,7 @@ enum class ProcessTerminationReason : uint8_t; class SandboxExtensionHandle; class WebPageProxy; +class WebProcessPool; class WebProcessProxy; class WebsiteDataStore; @@ -141,7 +142,7 @@ class GPUProcessProxy final : public AuxiliaryProcessProxy { #endif void updatePreferences(WebProcessProxy&); - void updateScreenPropertiesIfNeeded(); + void updateScreenPropertiesIfNeeded(WebProcessPool&); void childConnectionDidBecomeUnresponsive(); diff --git a/Source/WebKit/UIProcess/WebProcessPool.cpp b/Source/WebKit/UIProcess/WebProcessPool.cpp index 8a87dc01f738..058a2121042e 100644 --- a/Source/WebKit/UIProcess/WebProcessPool.cpp +++ b/Source/WebKit/UIProcess/WebProcessPool.cpp @@ -542,7 +542,7 @@ GPUProcessProxy& WebProcessPool::ensureGPUProcess() m_gpuProcess = gpuProcess.copyRef(); for (Ref process : m_processes) gpuProcess->updatePreferences(process); - gpuProcess->updateScreenPropertiesIfNeeded(); + gpuProcess->updateScreenPropertiesIfNeeded(*this); } return *m_gpuProcess; } @@ -1447,7 +1447,7 @@ Ref WebProcessPool::createWebPage(PageClient& pageClient, RefupdatePreferences(*process); - gpuProcess->updateScreenPropertiesIfNeeded(); + gpuProcess->updateScreenPropertiesIfNeeded(*this); } #endif diff --git a/Source/WebKit/UIProcess/WebProcessPool.h b/Source/WebKit/UIProcess/WebProcessPool.h index 65c5e2fed36e..21f1cb3b0106 100644 --- a/Source/WebKit/UIProcess/WebProcessPool.h +++ b/Source/WebKit/UIProcess/WebProcessPool.h @@ -64,6 +64,7 @@ OBJC_CLASS WKProcessPoolWeakObserver; #if PLATFORM(MAC) #include +#include #include #endif @@ -106,6 +107,7 @@ enum class GamepadHapticEffectType : uint8_t; enum class ProcessSwapDisposition : uint8_t; struct GamepadEffectParameters; struct MockMediaDevice; +struct ScreenProperties; #if PLATFORM(COCOA) class PowerSourceNotifier; #endif @@ -259,6 +261,7 @@ class WebProcessPool final #endif #if PLATFORM(MAC) + const WebCore::ScreenProperties& cachedScreenProperties(); void displayPropertiesChanged(WebCore::PlatformDisplayID, CGDisplayChangeSummaryFlags); #endif @@ -743,7 +746,15 @@ class WebProcessPool final void clearAudibleActivity(); #if PLATFORM(COCOA) + enum class ScreenPropertiesState : uint8_t { + Idle, + Collecting, + CollectingWithUpdatePending, + }; + void screenPropertiesUpdateTimerFired(); + void didCollectScreenProperties(WebCore::ScreenProperties&&); + void applyEDRSuppressionIfNeeded(WebCore::ScreenProperties&); #endif #if PLATFORM(IOS_FAMILY) @@ -1027,6 +1038,11 @@ class WebProcessPool final ApproximateTime m_lastScreenPropertiesUpdateTime; RunLoop::Timer m_screenPropertiesUpdateTimer; + ScreenPropertiesState m_screenPropertiesState { ScreenPropertiesState::Idle }; +#endif + +#if PLATFORM(MAC) + std::optional m_cachedScreenProperties; #endif #if ENABLE(IPC_TESTING_API) diff --git a/Tools/TestWebKitAPI/PlatformCocoa.cmake b/Tools/TestWebKitAPI/PlatformCocoa.cmake index c061c1922da1..b10f0fcf0797 100644 --- a/Tools/TestWebKitAPI/PlatformCocoa.cmake +++ b/Tools/TestWebKitAPI/PlatformCocoa.cmake @@ -190,6 +190,7 @@ list(APPEND TestWebKit_SOURCES Tests/WebCore/TestPlatformStrategies.cpp Tests/WebCore/cocoa/ISOBMFFTrackInfoParserTests.cpp + Tests/WebCore/cocoa/PlatformScreenTests.mm Tests/WebKit/WKWebView/WKBackForwardListTests.mm ) diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj index a83fd09fa86d..863deafa44b3 100644 --- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj +++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj @@ -738,6 +738,7 @@ WebCore/cocoa/ISOBMFFTrackInfoParserTests.cpp, WebCore/cocoa/MediaPlayerPrivateAVFoundationObjCTests.mm, WebCore/cocoa/MediaRecorderPrivateWriterTests.cpp, + WebCore/cocoa/PlatformScreenTests.mm, WebCore/cocoa/PrivateClickMeasurementCocoa.mm, WebCore/cocoa/ResourceMonitor.mm, WebCore/cocoa/ScrollbarWidthCrash.mm, diff --git a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/PlatformScreenTests.mm b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/PlatformScreenTests.mm new file mode 100644 index 000000000000..6d7a35047c38 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/PlatformScreenTests.mm @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" + +#if PLATFORM(COCOA) + +#import "Helpers/Test.h" +#import "Helpers/Utilities.h" +#import +#import +#import + +namespace TestWebKitAPI { + +TEST(PlatformScreen, CollectScreenPropertiesAsync) +{ + auto expectedProperties = WebCore::collectScreenProperties(); + + bool done = false; + std::optional actualProperties; + WebCore::collectScreenPropertiesAsync([&](WebCore::ScreenProperties&& properties) { + EXPECT_TRUE(isMainThread()); + actualProperties = WTF::move(properties); + done = true; + }); + + EXPECT_FALSE(done); + + Util::run(&done); + + ASSERT_TRUE(actualProperties.has_value()); + EXPECT_EQ(actualProperties->primaryDisplayID, expectedProperties.primaryDisplayID); + EXPECT_EQ(actualProperties->screenDataMap.size(), expectedProperties.screenDataMap.size()); + + // Only the dynamic range state is collected differently by the two functions, so limit the + // comparison to that. The other properties might differ (e.g. currentEDRHeadroom is affected by + // display brightness). + for (auto& [displayID, expectedScreenData] : expectedProperties.screenDataMap) { + auto iterator = actualProperties->screenDataMap.find(displayID); + ASSERT_TRUE(iterator != actualProperties->screenDataMap.end()); + EXPECT_EQ(iterator->value.screenSupportsHighDynamicRange, expectedScreenData.screenSupportsHighDynamicRange); +#if PLATFORM(MAC) + EXPECT_EQ(iterator->value.preferredDynamicRangeMode, expectedScreenData.preferredDynamicRangeMode); +#endif + } +} + +} // namespace TestWebKitAPI + +#endif // PLATFORM(COCOA) From 929f6e81e0e0881cde4edc161df05074f32541b5 Mon Sep 17 00:00:00 2001 From: Keith Miller Date: Fri, 28 Aug 2026 10:29:43 -0700 Subject: [PATCH 053/103] [Wasm] Exclude wasmBoundsCheckingSizeRegister from the callee saves restored https://bugs.webkit.org/show_bug.cgi?id=317654 rdar://177693309 Reviewed by Yijia Huang. GPRInfo::wasmBoundsCheckingSizeRegister (callee save) is only pinned in B3 when the OMG callee is compiled for MemoryMode::BoundsChecking. In Signaling mode it stays in B3/Air's mutable register set, and createTailCallPatchpoint declares the full callee-save set as clobberEarly so that B3 does not place an input there before the tail-call's parallel move runs. That clobberEarly causes AirHandleCalleeSaves to include regCS4 in the function's calleeSaveRegisterAtOffsetList(). The OMG prologue saves wasmBoundsCheckingSizeRegister to the callee save list and when making a tail call that callee save is restored after the callee's memory bounds are set. The wasm ABI already treats the pinned registers as effectively caller-save across wasm-to-wasm calls. Tail calls do not restore them either. Restoring wasmBoundsCheckingSizeRegister to the prologue-saved caller value in prepareForTailCallImpl is therefore unnecessary. This patch teaches emitRestoreCalleeSavesFor to take a dontRestoreRegisters set and uses it from prepareForTailCallImpl to skip wasmBoundsCheckingSizeRegister. I also added a FIXME at the pinRegister site noting that wasmBoundsCheckingSizeRegister is effectively caller-save in the wasm ABI. Originally-landed-as: 305413.1027@safari-7624.5-branch (654718255548). rdar://185368944 Canonical link: https://commits.webkit.org/320061@main --- JSTests/wasm/stress/tail-call-unused-pins.js | 50 +++++++++++++++++++ .../wasm/WasmOMGIRGenerator.cpp | 8 +++ 2 files changed, 58 insertions(+) create mode 100644 JSTests/wasm/stress/tail-call-unused-pins.js diff --git a/JSTests/wasm/stress/tail-call-unused-pins.js b/JSTests/wasm/stress/tail-call-unused-pins.js new file mode 100644 index 000000000000..fe5393ffe61d --- /dev/null +++ b/JSTests/wasm/stress/tail-call-unused-pins.js @@ -0,0 +1,50 @@ +//@ requireOptions("--useWasmFastMemory=true") +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +const watA = ` +(module + (type $sig (func (param i32 i64))) + (import "e" "mem" (memory 1 1)) + (import "e" "nop" (func $nop)) + (table (export "tbl") 1 1 funcref) + (func (export "f") (param $do_call i32) (param $off i32) (param $val i64) + (call $nop) + (if (local.get $do_call) + (then (return_call_indirect (type $sig) + (local.get $off) (local.get $val) (i32.const 0)))))) +`; + +const watB = ` +(module + (import "e" "mem" (memory 1 1)) + (func (export "g") (param $off i32) (param $val i64) + (i64.store (local.get $off) (local.get $val)))) +`; + +async function test() { + const memA = createWebAssemblyMemoryWithMode({ initial: 1, maximum: 1 }, "Signaling"); + const memB = createWebAssemblyMemoryWithMode({ initial: 1, maximum: 1 }, "BoundsChecking"); + assert.eq(WebAssemblyMemoryMode(memA), "Signaling"); + assert.eq(WebAssemblyMemoryMode(memB), "BoundsChecking"); + + const instA = await instantiate(watA, { e: { mem: memA, nop: () => {} } }, { tail_call: true }); + const { f, tbl } = instA.exports; + + const instB = await instantiate(watB, { e: { mem: memB } }); + const { g } = instB.exports; + + assert.throws(() => g(0x20000, 0n), WebAssembly.RuntimeError, "Out of bounds memory access"); + + // Tier g into BBQ. IPInt's prologue reloads regCS4 on entry but BBQ does not. + for (let i = 0; i < wasmTestLoopCount; ++i) g(0, 0n); + + // Tier f to OMG without ever taking the tail-call branch. + for (let i = 0; i < wasmTestLoopCount; ++i) f(0, 0, 0n); + + tbl.set(0, g); + + assert.throws(() => f(1, 0x20000, 0n), WebAssembly.RuntimeError, "Out of bounds memory access"); +} + +await assert.asyncTest(test()); diff --git a/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp index e9cbe8ce157d..c9f887518938 100644 --- a/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp +++ b/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp @@ -1328,6 +1328,9 @@ OMGIRGenerator::OMGIRGenerator(AbstractHeapRepository& heaps, CompilationContext m_proc.pinRegister(GPRInfo::wasmContextInstancePointer); m_proc.pinRegister(GPRInfo::wasmBaseMemoryPointer); + // FIXME: The wasm ABI effectively has to assume this is a caller save when getting + // called by wasm, so there's no point in saving and restoring it if B3 chooses to + // use it. We actively don't restore this register in many cases anyway e.g. tail calls. if (mode == MemoryMode::BoundsChecking) m_proc.pinRegister(GPRInfo::wasmBoundsCheckingSizeRegister); @@ -5841,6 +5844,11 @@ static inline void prepareForTailCallImpl(unsigned functionIndex, CCallHelpers& entries.reserveInitialCapacity(calleeSaves.registerCount() + functionSignature.argumentCount() + 1); for (const auto& regAtOffset : calleeSaves) { + // Don't restore wasmBoundsCheckingSizeRegister since we may have set it when checking for + // a cross-instance call. It's not a normal callee save independent of whether we used + // it or not. + if (regAtOffset.reg() == GPRInfo::wasmBoundsCheckingSizeRegister) + continue; ShuffleEntry entry; entry.src = ShuffleLocation::fromStack(fpOffsetToSPOffset(regAtOffset.offset())); if (regAtOffset.reg().isGPR()) { From d95a22ac27020e2a85d22ef67e683de0de274b3e Mon Sep 17 00:00:00 2001 From: Sergey Rubanov Date: Fri, 28 Aug 2026 10:37:23 -0700 Subject: [PATCH 054/103] [JSC][Wasm] Cover elem kind 6 initialized from global.get Reviewed by Yusuke Suzuki. The parser and table init already support Element::FromGlobal. Enable the commented kind-6 global.get case and collect wasm/extended-const so EWS runs it. * JSTests/wasm/extended-const/extended-const.js: * JSTests/wasm.yaml: Canonical link: https://commits.webkit.org/320062@main --- JSTests/wasm.yaml | 2 ++ JSTests/wasm/extended-const/extended-const.js | 25 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/JSTests/wasm.yaml b/JSTests/wasm.yaml index ae481b41bdbb..80d6f5fa6df7 100644 --- a/JSTests/wasm.yaml +++ b/JSTests/wasm.yaml @@ -53,6 +53,8 @@ cmd: runV8WebAssemblySuite(:no_module, "mjsunit.js") unless parseRunCommands - path: wasm/branch-hints cmd: runWebAssemblySuite unless parseRunCommands +- path: wasm/extended-const + cmd: runWebAssemblySuite unless parseRunCommands - path: wasm/threads-spec-tests cmd: runWebAssemblyThreadsSpecTest :normal diff --git a/JSTests/wasm/extended-const/extended-const.js b/JSTests/wasm/extended-const/extended-const.js index b273086a583d..76fbb68b516d 100644 --- a/JSTests/wasm/extended-const/extended-const.js +++ b/JSTests/wasm/extended-const/extended-const.js @@ -309,8 +309,7 @@ async function testExtendedConstElement() { assert.eq(m.exports.t.get(43), null); } - // FIXME: this requires changing how element segment initialization vectors are parsed. - // Test element segment kind 6.with element init expression. + // Test element segment kind 6 with element init expression. /* * (module * (global (import "m" "gi1") externref) @@ -318,17 +317,17 @@ async function testExtendedConstElement() { * (elem (table 0) (offset (i32.add (i32.const 1) (i32.const 42))) externref (global.get 0)) * ) */ - //{ - // let obj = "hello"; - // let m = new WebAssembly.Instance( - // module("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x8a\x80\x80\x80\x00\x01\x01\x6d\x03\x67\x69\x31\x03\x6f\x00\x04\x84\x80\x80\x80\x00\x01\x6f\x00\x40\x07\x85\x80\x80\x80\x00\x01\x01\x74\x01\x00\x09\x8e\x80\x80\x80\x00\x01\x06\x00\x41\x01\x41\x2a\x6a\x0b\x6f\x01\x23\x00\x0b"), - // { m: { gi1: obj } } - // ); - // assert.eq(m.exports.t.get(0), null); - // assert.eq(m.exports.t.get(42), null); - // assert.eq(m.exports.t.get(43), obj); - // assert.eq(m.exports.t.get(44), null); - //} + { + let obj = "hello"; + let m = new WebAssembly.Instance( + module("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x8a\x80\x80\x80\x00\x01\x01\x6d\x03\x67\x69\x31\x03\x6f\x00\x04\x84\x80\x80\x80\x00\x01\x6f\x00\x40\x07\x85\x80\x80\x80\x00\x01\x01\x74\x01\x00\x09\x8e\x80\x80\x80\x00\x01\x06\x00\x41\x01\x41\x2a\x6a\x0b\x6f\x01\x23\x00\x0b"), + { m: { gi1: obj } } + ); + assert.eq(m.exports.t.get(0), null); + assert.eq(m.exports.t.get(42), null); + assert.eq(m.exports.t.get(43), obj); + assert.eq(m.exports.t.get(44), null); + } } async function testExtendedConstData() { From 4b267e8636a2803d37d11d334f61be96093e5f5c Mon Sep 17 00:00:00 2001 From: lilly <173393835+cupidsity@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:38:53 -0700 Subject: [PATCH 055/103] corner-shape: backdrop-filter is clipped to the rounded rect, ignoring corner-shape https://bugs.webkit.org/show_bug.cgi?id=322509 rdar://185813432 Reviewed by Simon Fraser. Give the backdrop layer its own shape path, set from the outer contour when the corners aren't round, and drop the corners from backdropFiltersRect so a shape reaching outside the radius isn't clipped back to it. Passes existing tests * Source/WebCore/platform/graphics/GraphicsLayer.h: (WebCore::GraphicsLayer::setBackdropFiltersShapePath): (WebCore::GraphicsLayer::backdropFiltersShapePath const): * Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp: (WebCore::shapePathsAreEqual): (WebCore::GraphicsLayerCA::setContentsClipShapePath): (WebCore::GraphicsLayerCA::setBackdropFiltersShapePath): (WebCore::GraphicsLayerCA::updateBackdropFiltersRect): (WebCore::GraphicsLayerCA::ensureStructuralLayer): (WebCore::contentsClipShapePathsAreEqual): Deleted. * Source/WebCore/platform/graphics/ca/GraphicsLayerCA.h: * Source/WebCore/rendering/RenderLayerBacking.cpp: (WebCore::RenderLayerBacking::updateBackdropFiltersGeometry): * LayoutTests/TestExpectations: * LayoutTests/imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-overflow.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter.html: * Source/WebCore/platform/graphics/skia/SkiaCompositingLayer.cpp: (WebCore::SkiaCompositingLayer::paintBackdrop): * Source/WebCore/platform/graphics/skia/SkiaCompositingLayer.h: * Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp: (WebCore::CoordinatedPlatformLayer::setBackdropShapePath): (WebCore::CoordinatedPlatformLayer::flushCompositingStateOnTarget): (WebCore::CoordinatedPlatformLayer::flushCompositingStateOnSkiaTarget): * Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h: * Source/WebCore/platform/graphics/texmap/coordinated/GraphicsLayerCoordinated.cpp: (WebCore::GraphicsLayerCoordinated::setBackdropFiltersShapePath): (WebCore::GraphicsLayerCoordinated::commitLayerChanges): * Source/WebCore/platform/graphics/texmap/coordinated/GraphicsLayerCoordinated.h: Canonical link: https://commits.webkit.org/320063@main --- LayoutTests/TestExpectations | 2 -- ...corner-shape-backdrop-filter-overflow.html | 2 +- .../corner-shape-backdrop-filter.html | 2 +- .../WebCore/platform/graphics/GraphicsLayer.h | 4 +++ .../platform/graphics/ca/GraphicsLayerCA.cpp | 27 +++++++++++------ .../platform/graphics/ca/GraphicsLayerCA.h | 3 +- .../graphics/skia/SkiaCompositingLayer.cpp | 5 +++- .../graphics/skia/SkiaCompositingLayer.h | 2 ++ .../coordinated/CoordinatedPlatformLayer.cpp | 30 +++++++++++++++++++ .../coordinated/CoordinatedPlatformLayer.h | 3 ++ .../coordinated/GraphicsLayerCoordinated.cpp | 12 ++++++++ .../coordinated/GraphicsLayerCoordinated.h | 2 ++ .../WebCore/rendering/RenderLayerBacking.cpp | 8 +++++ 13 files changed, 87 insertions(+), 15 deletions(-) diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index accb808eea6d..feec1588be89 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -6745,9 +6745,7 @@ webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-stroke-from-border.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-two-shapes-shadow.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-outline-double.html [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-overflow.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-video-overflow.html [ ImageOnlyFailure Pass ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-clips-background.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-collapsed-shape-clips-background.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-geometry-box.html [ ImageOnlyFailure ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-overflow.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-overflow.html index 09efdaee8415..837c130a8569 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-overflow.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-overflow.html @@ -2,7 +2,7 @@ - + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/w3c-import.log index b748b09f5e71..fe8f5c8524bf 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/w3c-import.log @@ -50,6 +50,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-backdrop-appearance-expected.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-backdrop-appearance-ref.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-backdrop-appearance.html +/LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-beforetoggle-change-type-crash.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-beforetoggle-opening-event.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-change-type.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-checkbox-backdrop-expected.html @@ -71,6 +72,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-focus-inert-invoker.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-focus-inside-shadow-dom.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-focus-inside-slot.html +/LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-focus-invoker-inside-popover.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-focus-overflow-visible.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-focus-previous-crash.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-focus-slotted.html @@ -83,6 +85,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-hidden-display.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-hint-crash.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-hint-hierarchy.html +/LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-hint-loseinterest-show-child.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-hint-reentrant-crash.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-iframe-backdrop-expected.html /LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-iframe-backdrop.html From ef84d011acd78015778c666d39ab2b8b788bd44f Mon Sep 17 00:00:00 2001 From: Sammy Gill Date: Fri, 28 Aug 2026 11:50:28 -0700 Subject: [PATCH 058/103] GitHub.com: v2: emoji reaction overlaps code box in comment https://bugs.webkit.org/show_bug.cgi?id=312152 rdar://183465635 Reviewed by Alan Baradlay. In 314501@main we attempted this patch in order to fix the described problem on GitHub.com. This ended up causing a regression on flights.google.com so it ended up getting reverted. Here we are reattempting the same patch but with a fix for the regression. This commit message will focus on that regression fix almost the entirety of this patch is the same as the original so I will defer the explanation of the underlying architecture to that commit message. The main difference in this patch is that instead of calling setNeedsLayout() on the affected renderer and invalidating all the way up the containing block chain we invalidate only between that renderer and the subtree root. Specifically, instead of having ``` if (rendererScrollbarChange.sizesAffectedFromScrollbarChanges.contains(LogicalBoxAxis::Block)) renderer->setNeedsLayout(); ``` we have ``` if (rendererScrollbarChange.sizesAffectedFromScrollbarChanges.contains(LogicalBoxAxis::Block)) { renderer->setNeedsLayout(MarkingBehavior::MarkOnlyThis); renderer->markContainingBlocksForLayout(subtreeRoot.ptr()); } ``` Here is some markup from the reduced testcase that came out of flights.google.com: position: relative inline-block, width: 600px
display: flex, width: 500px, height: auto
width: 100px
position: relative, flex: 1
position: absolute, width: 100%, overflow-x: auto
width: 300px inline-block, position: relative
position: absolute, height: 20px #flexContainer is the subtree root: a block-level flex box with an auto block-size, so sizesAffectedByScrollbarsForSubtreeRoot() tracks its block axis. Widening #flexItem to 450px shrinks #positionedFlexItem, and therefore #scroller, to 50px, so #overflowingContent overflows and #scroller gains a horizontal scrollbar during #flexContainer's layout. The call to setNeedsLayout() on #scroller walks up the containing block chain and dirties the renderers past #flexContainer. For RenderBlock ancestors above the subtree root this is harmless. Layout is descending through them, so they already carry the bit and the walk early-outs, and anything that does get set is cleared on the way back out. For RenderInline this does not appear to be the case. Before we run inline layout we run some invalidation via RenderBlockFlow::markInlineContentDirtyForLayout(). In the above example we would call clearNeedsLayout on #positionedInline before running layout on the inline block. Then during the inline block's layout we end up dirtying #positionedInline again from the resulting scrollbar handling code. To get around this we can scope our invalidation only to the subtree root that is handling the scrollbar change. This is much more precise anyways since we know for sure we will be running layout again starting at that renderer. Canonical link: https://commits.webkit.org/320066@main --- LayoutTests/TestExpectations | 7 -- ...ut-with-scrollable-descendant-expected.txt | 4 ++ ...llable-descendant-vertical-lr-expected.txt | 4 ++ ...ith-scrollable-descendant-vertical-lr.html | 64 +++++++++++++++++ ...s-relayout-with-scrollable-descendant.html | 68 +++++++++++++++++++ Source/WebCore/rendering/RenderBlock.cpp | 18 +++-- .../SubtreeScrollbarChangesState.cpp | 11 ++- 7 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant.html diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index feec1588be89..98c5ba72abe9 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -4205,13 +4205,6 @@ imported/w3c/web-platform-tests/css/css-overflow/overflow-canvas.html [ ImageOnl imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-border-radius-002.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-border-radius.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/flex-column-container-with-scrollable-descendant.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/flex-container-multiple-items-with-scrollable-descendant.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/flex-container-with-scrollable-descendant.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/flex-nested-container-with-scrollable-descendant.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/grid-container-with-scrollable-descendant.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/grid-nested-container-with-scrollable-descendant.html [ ImageOnlyFailure ] - # Tests that failed on the 2026-02-02 import of css-overflow imported/w3c/web-platform-tests/css/css-overflow/clip-008.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/column-style-change-triggers-relayout.html [ ImageOnlyFailure ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-expected.txt new file mode 100644 index 000000000000..0fe501cd5f8b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-expected.txt @@ -0,0 +1,4 @@ + +PASS A horizontal scrollbar appears inside the flex container during its layout +PASS An out-of-flow box under the same positioned inline is relaid out after the scrollbar change + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr-expected.txt new file mode 100644 index 000000000000..f1d0a1bc8d9b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr-expected.txt @@ -0,0 +1,4 @@ + +PASS A vertical scrollbar appears inside the flex container during its layout +PASS An out-of-flow box under the same positioned inline is relaid out after the scrollbar change + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr.html new file mode 100644 index 000000000000..3d6a959ed6bd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant-vertical-lr.html @@ -0,0 +1,64 @@ + + +An absolutely positioned box is still relaid out after a scrollable descendant gains a scrollbar, in vertical-lr + + + +
+ + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant.html new file mode 100644 index 000000000000..ce64c13dd32d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/abspos-relayout-with-scrollable-descendant.html @@ -0,0 +1,68 @@ + + +An absolutely positioned box is still relaid out after a scrollable descendant gains a scrollbar + + + +
+ + + + diff --git a/Source/WebCore/rendering/RenderBlock.cpp b/Source/WebCore/rendering/RenderBlock.cpp index bf0899f1cead..ea559e8d68bf 100644 --- a/Source/WebCore/rendering/RenderBlock.cpp +++ b/Source/WebCore/rendering/RenderBlock.cpp @@ -556,18 +556,22 @@ static EnumSet sizesAffectedByScrollbarsForSubtreeRoot(const Ren if (layoutContext.subtreeScrollbarChangesState()) return { }; + EnumSet sizesAffected; + auto& style = renderBlock.style(); auto& computedLogicalWidth = style.logicalWidth(); - if (computedLogicalWidth.isFixed()) - return { }; + if (!computedLogicalWidth.isFixed() && (computedLogicalWidth.isIntrinsic() || computedLogicalWidth.isMinIntrinsic() || renderBlock.sizesLogicalWidthToFitContent())) + sizesAffected.add(LogicalBoxAxis::Inline); - if (computedLogicalWidth.isIntrinsic() || computedLogicalWidth.isMinIntrinsic()) - return LogicalBoxAxis::Inline; + auto& computedLogicalHeight = style.logicalHeight(); - if (renderBlock.sizesLogicalWidthToFitContent()) - return LogicalBoxAxis::Inline; + if (style.display().isFlexibleBox() && renderBlock.isBlockLevelBox() && (computedLogicalHeight.isAuto() || computedLogicalHeight.isIntrinsic())) + sizesAffected.add(LogicalBoxAxis::Block); - return { }; + if (renderBlock.isRenderGrid() && (computedLogicalHeight.isAuto() || computedLogicalHeight.isIntrinsic())) + sizesAffected.add(LogicalBoxAxis::Block); + + return sizesAffected; } static bool canContainDescendantScrollbarChanges(const RenderBlock& renderBlock, const LocalFrameViewLayoutContext& layoutContext) diff --git a/Source/WebCore/rendering/SubtreeScrollbarChangesState.cpp b/Source/WebCore/rendering/SubtreeScrollbarChangesState.cpp index 660d30e96a15..defff36b4891 100644 --- a/Source/WebCore/rendering/SubtreeScrollbarChangesState.cpp +++ b/Source/WebCore/rendering/SubtreeScrollbarChangesState.cpp @@ -147,9 +147,14 @@ SubtreeScrollbarChangesHandler::~SubtreeScrollbarChangesHandler() auto& subtreeRoot = subtreeScrollbarChangesState->subtreeRoot; for (auto& rendererScrollbarChange : descendantsWithScrollbarChange) { - if (rendererScrollbarChange.sizesAffectedFromScrollbarChanges.containsOnly(LogicalBoxAxis::Block)) - continue; - protect(rendererScrollbarChange.renderer)->invalidateContentLogicalWidths(MarkingBehavior::MarkContainingBlockChain, protect(subtreeRoot->containingBlock())); + CheckedRef renderer = rendererScrollbarChange.renderer; + ASSERT(renderer->isDescendantOf(subtreeRoot.ptr())); + if (rendererScrollbarChange.sizesAffectedFromScrollbarChanges.contains(LogicalBoxAxis::Block)) { + renderer->setNeedsLayout(MarkingBehavior::MarkOnlyThis); + renderer->markContainingBlocksForLayout(subtreeRoot.ptr()); + } + if (rendererScrollbarChange.sizesAffectedFromScrollbarChanges.contains(LogicalBoxAxis::Inline)) + renderer->invalidateContentLogicalWidths(MarkingBehavior::MarkContainingBlockChain, protect(subtreeRoot->containingBlock())); } descendantsWithScrollbarChange.clear(); From bc80ea5383e3e5e759386744a2a5b5eec88d18b9 Mon Sep 17 00:00:00 2001 From: Sam Sneddon Date: Fri, 28 Aug 2026 12:15:31 -0700 Subject: [PATCH 059/103] [perf.webkit.org] Update sync-commits.py to support Canonical-link https://bugs.webkit.org/show_bug.cgi?id=316832 rdar://179276740 Reviewed by Dewei Zhu. This follows the same pattern we're using elsewhere: convert to hyphened form, and then parse with parse-trailers. * Websites/perf.webkit.org/tools/sync-commits.py: (GitRepository._revision_from_tokens): Canonical link: https://commits.webkit.org/320067@main --- Websites/perf.webkit.org/tools/sync-commits.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Websites/perf.webkit.org/tools/sync-commits.py b/Websites/perf.webkit.org/tools/sync-commits.py index 126076e354ed..e7b709e3e1e4 100755 --- a/Websites/perf.webkit.org/tools/sync-commits.py +++ b/Websites/perf.webkit.org/tools/sync-commits.py @@ -19,7 +19,7 @@ # There are some buggy commit messages: # Canonical link: https://commits.webkit.org/https://commits.webkit.org/232477@main -REVISION_IDENTIFIER_IN_MSG_RE = re.compile(r'^Canonical link: (https\://commits\.webkit\.org/)+(?P\d+\.?\d*@(?P[\w\.\-]+))\n', flags=re.MULTILINE) +REVISION_IDENTIFIER_IN_MSG_RE = re.compile(r'^Canonical-link: (https\://commits\.webkit\.org/)+(?P\d+\.?\d*@(?P[\w\.\-]+))\n', flags=re.MULTILINE) REVISION_IN_MSG_RE = re.compile(r'^git-svn-id: https://svn\.webkit\.org/repository/webkit/[\w\W]+@(?P\d+) [\w\d\-]+\n', flags=re.MULTILINE) HASH_RE = re.compile(r'^[a-f0-9A-F]+$') REVISION_RE = re.compile(r'^[Rr]?(?P\d+)$') @@ -266,7 +266,16 @@ def _revision_from_tokens(self, tokens): revision_identifier = None if self._report_revision_identifier_in_commit_msg: - for revision_identifier_match in REVISION_IDENTIFIER_IN_MSG_RE.finditer(message): + trailer_output = subprocess.check_output( + ['git', '-C', self._git_checkout, + '-c', 'trailer.Canonical-link.key=Canonical-link', + '-c', 'trailer.Identifier.key=Identifier', + '-c', 'trailer.git-svn-id.key=git-svn-id', + 'interpret-trailers', '--parse', '--no-divider'], + input=message.replace('\nCanonical link:', '\nCanonical-link:'), + encoding='utf-8', + ) + for revision_identifier_match in REVISION_IDENTIFIER_IN_MSG_RE.finditer(trailer_output): if self._git_branch and revision_identifier_match.group('branch_name') != self._git_branch: continue revision_identifier = revision_identifier_match.group('revision_identifier') From 7418d7f57da4fb7271efe147368878ed070cdd9d Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Fri, 28 Aug 2026 12:34:43 -0700 Subject: [PATCH 060/103] Change movingSteps isSubtreeRoot parameter to an enum https://bugs.webkit.org/show_bug.cgi?id=321174 Reviewed by Ryosuke Niwa. Replaces the old bool parameter with a new enum class instead. * Source/WebCore/dom/ContainerNode.cpp: (WebCore::runMovingStepsForShadowIncludingInclusiveDescendants): * Source/WebCore/dom/Element.cpp: (WebCore::Element::movingSteps): * Source/WebCore/dom/Element.h: * Source/WebCore/dom/Node.cpp: (WebCore::Node::movingSteps): * Source/WebCore/dom/Node.h: * Source/WebCore/html/HTMLImageElement.cpp: (WebCore::HTMLImageElement::movingSteps): * Source/WebCore/html/HTMLImageElement.h: * Source/WebCore/html/HTMLOptionElement.cpp: (WebCore::HTMLOptionElement::movingSteps): * Source/WebCore/html/HTMLOptionElement.h: * Source/WebCore/html/HTMLSourceElement.cpp: (WebCore::HTMLSourceElement::movingSteps): * Source/WebCore/html/HTMLSourceElement.h: Canonical link: https://commits.webkit.org/320068@main --- Source/WebCore/dom/ContainerNode.cpp | 2 +- Source/WebCore/dom/Element.cpp | 4 ++-- Source/WebCore/dom/Element.h | 2 +- Source/WebCore/dom/Node.cpp | 2 +- Source/WebCore/dom/Node.h | 6 +++++- Source/WebCore/html/HTMLImageElement.cpp | 4 ++-- Source/WebCore/html/HTMLImageElement.h | 2 +- Source/WebCore/html/HTMLOptionElement.cpp | 4 ++-- Source/WebCore/html/HTMLOptionElement.h | 2 +- Source/WebCore/html/HTMLSourceElement.cpp | 4 ++-- Source/WebCore/html/HTMLSourceElement.h | 2 +- 11 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Source/WebCore/dom/ContainerNode.cpp b/Source/WebCore/dom/ContainerNode.cpp index b929ed75363b..5f97bb1e1320 100644 --- a/Source/WebCore/dom/ContainerNode.cpp +++ b/Source/WebCore/dom/ContainerNode.cpp @@ -1449,7 +1449,7 @@ static void runMovingStepsForShadowIncludingInclusiveDescendants(Node& root, Nod for (RefPtr inclusiveDescendant = &root; inclusiveDescendant; inclusiveDescendant = NodeTraversal::next(*inclusiveDescendant, &root)) { bool isSubtreeRoot = inclusiveDescendant.get() == &movedNode; - inclusiveDescendant->movingSteps(isSubtreeRoot, oldParent); + inclusiveDescendant->movingSteps(isSubtreeRoot ? Node::IsSubtreeRoot::Yes : Node::IsSubtreeRoot::No, oldParent); if (newParentIsConnected) { if (RefPtr element = dynamicDowncast(*inclusiveDescendant); element && element->isDefinedCustomElement()) diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp index 11e8a70e9e56..7b0be72972df 100644 --- a/Source/WebCore/dom/Element.cpp +++ b/Source/WebCore/dom/Element.cpp @@ -3325,7 +3325,7 @@ void Element::removingSteps(RemovalType removalType, ContainerNode& oldParentOfR } } -void Element::movingSteps(bool isSubtreeRoot, ContainerNode& oldParent) +void Element::movingSteps(IsSubtreeRoot isSubtreeRoot, ContainerNode& oldParent) { ContainerNode::movingSteps(isSubtreeRoot, oldParent); @@ -3359,7 +3359,7 @@ void Element::movingSteps(bool isSubtreeRoot, ContainerNode& oldParent) updateEffectiveLangState(); - if (!isSubtreeRoot || !hasFocusWithin()) + if (isSubtreeRoot == IsSubtreeRoot::No || !hasFocusWithin()) return; if (RefPtr oldParentElement = dynamicDowncast(oldParent)) diff --git a/Source/WebCore/dom/Element.h b/Source/WebCore/dom/Element.h index b892c81e730f..aa9cec4722f3 100644 --- a/Source/WebCore/dom/Element.h +++ b/Source/WebCore/dom/Element.h @@ -940,7 +940,7 @@ class Element : public ContainerNode { NeedsPostConnectionSteps insertionSteps(InsertionType, ContainerNode&) override; void removingSteps(RemovalType, ContainerNode&) override; - void movingSteps(bool, ContainerNode&) override; + void movingSteps(IsSubtreeRoot, ContainerNode&) override; void childrenChanged(const ChildChange&) override; void removeAllEventListeners() override; diff --git a/Source/WebCore/dom/Node.cpp b/Source/WebCore/dom/Node.cpp index 359a3bd81f9a..4ded6f826aed 100644 --- a/Source/WebCore/dom/Node.cpp +++ b/Source/WebCore/dom/Node.cpp @@ -1518,7 +1518,7 @@ void Node::removingSteps(RemovalType removalType, ContainerNode& oldParentOfRemo } } -void Node::movingSteps(bool, ContainerNode&) +void Node::movingSteps(IsSubtreeRoot, ContainerNode&) { invalidateStyle(Style::Validity::SubtreeInvalid, Style::InvalidationMode::InsertedIntoAncestor); } diff --git a/Source/WebCore/dom/Node.h b/Source/WebCore/dom/Node.h index cbf18175cc8b..87db8583d740 100644 --- a/Source/WebCore/dom/Node.h +++ b/Source/WebCore/dom/Node.h @@ -505,8 +505,12 @@ class Node : public EventTarget, public CanMakeCheckedPtr { // https://dom.spec.whatwg.org/#concept-node-remove-ext virtual void removingSteps(RemovalType, ContainerNode& oldParentOfRemovedTree); + enum class IsSubtreeRoot { + Yes, + No + }; // https://dom.spec.whatwg.org/#concept-node-move-ext - virtual void movingSteps(bool, ContainerNode&); + virtual void movingSteps(IsSubtreeRoot, ContainerNode&); void updateShadowIncludingRootForSubtree(); diff --git a/Source/WebCore/html/HTMLImageElement.cpp b/Source/WebCore/html/HTMLImageElement.cpp index 8d954ae00906..51f1e3ad6a46 100644 --- a/Source/WebCore/html/HTMLImageElement.cpp +++ b/Source/WebCore/html/HTMLImageElement.cpp @@ -631,11 +631,11 @@ void HTMLImageElement::removingSteps(RemovalType removalType, ContainerNode& old FormAssociatedElement::elementRemovedFromAncestor(*this, removalType); } -void HTMLImageElement::movingSteps(bool isSubtreeRoot, ContainerNode& oldParent) +void HTMLImageElement::movingSteps(IsSubtreeRoot isSubtreeRoot, ContainerNode& oldParent) { HTMLElement::movingSteps(isSubtreeRoot, oldParent); - if (!isSubtreeRoot) + if (isSubtreeRoot == IsSubtreeRoot::No) return; if (RefPtr parentPicture = dynamicDowncast(parentElement())) { diff --git a/Source/WebCore/html/HTMLImageElement.h b/Source/WebCore/html/HTMLImageElement.h index f5798ad1744a..731e48c7b622 100644 --- a/Source/WebCore/html/HTMLImageElement.h +++ b/Source/WebCore/html/HTMLImageElement.h @@ -218,7 +218,7 @@ class HTMLImageElement NeedsPostConnectionSteps insertionSteps(InsertionType, ContainerNode&) override; void removingSteps(RemovalType, ContainerNode&) override; - void movingSteps(bool isSubtreeRoot, ContainerNode&) override; + void movingSteps(IsSubtreeRoot, ContainerNode&) override; bool NODELETE isFormListedElement() const final { return false; } FormAssociatedElement* NODELETE asFormAssociatedElement() final { return this; } diff --git a/Source/WebCore/html/HTMLOptionElement.cpp b/Source/WebCore/html/HTMLOptionElement.cpp index 08c4a13d6161..1bde2dab925a 100644 --- a/Source/WebCore/html/HTMLOptionElement.cpp +++ b/Source/WebCore/html/HTMLOptionElement.cpp @@ -211,11 +211,11 @@ void HTMLOptionElement::removingSteps(RemovalType removalType, ContainerNode& ol } } -void HTMLOptionElement::movingSteps(bool isSubtreeRoot, ContainerNode& oldParent) +void HTMLOptionElement::movingSteps(IsSubtreeRoot isSubtreeRoot, ContainerNode& oldParent) { HTMLElement::movingSteps(isSubtreeRoot, oldParent); - if (!isSubtreeRoot) + if (isSubtreeRoot == IsSubtreeRoot::No) return; if (!document().settings().htmlEnhancedSelectParsingEnabled()) diff --git a/Source/WebCore/html/HTMLOptionElement.h b/Source/WebCore/html/HTMLOptionElement.h index 8ade2c0b0dd7..8d513f26c956 100644 --- a/Source/WebCore/html/HTMLOptionElement.h +++ b/Source/WebCore/html/HTMLOptionElement.h @@ -87,7 +87,7 @@ class HTMLOptionElement final : public HTMLElement { NeedsPostConnectionSteps insertionSteps(InsertionType, ContainerNode&) final; void removingSteps(RemovalType, ContainerNode& oldParentOfRemovedTree) final; - void movingSteps(bool isSubtreeRoot, ContainerNode&) final; + void movingSteps(IsSubtreeRoot, ContainerNode&) final; bool supportsFocus() const final; bool isFocusable() const final; diff --git a/Source/WebCore/html/HTMLSourceElement.cpp b/Source/WebCore/html/HTMLSourceElement.cpp index 8fa9cdeaab9a..3120e9932c7d 100644 --- a/Source/WebCore/html/HTMLSourceElement.cpp +++ b/Source/WebCore/html/HTMLSourceElement.cpp @@ -124,11 +124,11 @@ void HTMLSourceElement::removingSteps(RemovalType removalType, ContainerNode& ol } } -void HTMLSourceElement::movingSteps(bool isSubtreeRoot, ContainerNode& oldParent) +void HTMLSourceElement::movingSteps(IsSubtreeRoot isSubtreeRoot, ContainerNode& oldParent) { HTMLElement::movingSteps(isSubtreeRoot, oldParent); - if (!isSubtreeRoot) + if (isSubtreeRoot == IsSubtreeRoot::No) return; RefPtr oldParentPicture = dynamicDowncast(oldParent); diff --git a/Source/WebCore/html/HTMLSourceElement.h b/Source/WebCore/html/HTMLSourceElement.h index b8313a6888bc..c8710b63c76c 100644 --- a/Source/WebCore/html/HTMLSourceElement.h +++ b/Source/WebCore/html/HTMLSourceElement.h @@ -60,7 +60,7 @@ class HTMLSourceElement final NeedsPostConnectionSteps insertionSteps(InsertionType, ContainerNode&) final; void removingSteps(RemovalType, ContainerNode&) final; - void movingSteps(bool isSubtreeRoot, ContainerNode&) final; + void movingSteps(IsSubtreeRoot, ContainerNode&) final; void didMoveToNewDocument(Document& oldDocument, Document& newDocument) final; bool NODELETE isURLAttribute(const Attribute&) const final; From 0ac697f1a0640b799851de96337bf5f7e7a997cf Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Fri, 28 Aug 2026 12:35:24 -0700 Subject: [PATCH 061/103] Add new ChildChange types for moveBefore https://bugs.webkit.org/show_bug.cgi?id=316098 Reviewed by Ryosuke Niwa and Darin Adler. This adds a new ElementMoved, TextMoved, and NonContentsChildMoved ChildChange type. These are used by moveBefore(), this ensures that children changed steps such as script's which runs prepareScript, don't run when triggered via moveBefore. * LayoutTests/TestExpectations: * LayoutTests/imported/w3c/web-platform-tests/dom/nodes/moveBefore/script-move-before-expected.txt: * Source/WebCore/dom/ContainerNode.cpp: (WebCore::makeChildChangeForMoveRemoval): (WebCore::makeChildChangeForMoveInsertion): (WebCore::ContainerNode::moveBefore): * Source/WebCore/dom/ContainerNode.h: (WebCore::ContainerNode::ChildChange::isInsertion const): * Source/WebCore/dom/Element.cpp: (WebCore::Element::childrenChanged): * Source/WebCore/dom/ShadowRoot.cpp: (WebCore::ShadowRoot::childrenChanged): * Source/WebCore/html/HTMLSelectElement.cpp: (WebCore::HTMLSelectElement::optionToSelectFromChildChangeScope): * Source/WebCore/svg/SVGAnimateMotionElement.cpp: (WebCore::SVGAnimateMotionElement::childrenChanged): Canonical link: https://commits.webkit.org/320069@main --- LayoutTests/TestExpectations | 3 -- .../script-move-before-expected.txt | 4 +- Source/WebCore/dom/ContainerNode.cpp | 49 +++++++++++++++++-- Source/WebCore/dom/ContainerNode.h | 8 ++- Source/WebCore/dom/Element.cpp | 7 +++ Source/WebCore/dom/ShadowRoot.cpp | 6 +++ Source/WebCore/html/HTMLSelectElement.cpp | 2 +- .../WebCore/svg/SVGAnimateMotionElement.cpp | 6 +++ 8 files changed, 74 insertions(+), 11 deletions(-) diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index 98c5ba72abe9..d4790eaa4cfe 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -6232,9 +6232,6 @@ imported/w3c/web-platform-tests/trusted-types/should-trusted-type-policy-creatio webkit.org/b/281223 imported/w3c/web-platform-tests/dom/nodes/moveBefore/focus-preserve-render.html [ Skip ] webkit.org/b/281223 imported/w3c/web-platform-tests/dom/nodes/moveBefore/moveBefore-option-recalc-style.html [ Skip ] -# Flaky crash. -webkit.org/b/315031 imported/w3c/web-platform-tests/dom/nodes/moveBefore/throws-exception.html [ Skip ] - # Flaky. imported/w3c/web-platform-tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-default-style-meta-from-fragment.html [ Skip ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/nodes/moveBefore/script-move-before-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/nodes/moveBefore/script-move-before-expected.txt index 6a269137f591..b167f7286f86 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/nodes/moveBefore/script-move-before-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/nodes/moveBefore/script-move-before-expected.txt @@ -1,4 +1,4 @@ -FAIL Synchronous script execution in HTMLScriptElement during moveBefore should be blocked assert_false: does not define moving steps which allow script execution. expected false got true -FAIL Synchronous script execution in SVGScriptElement during moveBefore should be blocked assert_false: does not define moving steps which allow script execution. expected false got true +PASS Synchronous script execution in HTMLScriptElement during moveBefore should be blocked +PASS Synchronous script execution in SVGScriptElement during moveBefore should be blocked diff --git a/Source/WebCore/dom/ContainerNode.cpp b/Source/WebCore/dom/ContainerNode.cpp index 5f97bb1e1320..059fd4f0f380 100644 --- a/Source/WebCore/dom/ContainerNode.cpp +++ b/Source/WebCore/dom/ContainerNode.cpp @@ -312,6 +312,49 @@ static ContainerNode::ChildChange makeChildChangeForInsertion(ContainerNode& con }; } +static ContainerNode::ChildChange makeChildChangeForMoveRemoval(Node& child) +{ + auto changeType = [&] { + if (is(child)) + return ContainerNode::ChildChange::Type::ElementMovedFrom; + if (is(child)) + return ContainerNode::ChildChange::Type::TextMovedFrom; + return ContainerNode::ChildChange::Type::NonContentsChildMovedFrom; + }(); + + return { + changeType, + nullptr, + dynamicDowncast(child), + ElementTraversal::previousSibling(child), + ElementTraversal::nextSibling(child), + ContainerNode::ChildChange::Source::API, + changeType == ContainerNode::ChildChange::Type::ElementMovedFrom ? ContainerNode::ChildChange::AffectsElements::Yes : ContainerNode::ChildChange::AffectsElements::No + }; +} + +static ContainerNode::ChildChange makeChildChangeForMoveInsertion(ContainerNode& containerNode, Node& child, Node* beforeChild) +{ + auto changeType = [&] { + if (is(child)) + return ContainerNode::ChildChange::Type::ElementMovedInto; + if (is(child)) + return ContainerNode::ChildChange::Type::TextMovedInto; + return ContainerNode::ChildChange::Type::NonContentsChildMovedInto; + }(); + + auto* beforeChildElement = dynamicDowncast(beforeChild); + return { + changeType, + nullptr, + dynamicDowncast(child), + beforeChild ? ElementTraversal::previousSibling(*beforeChild) : ElementTraversal::lastChild(containerNode), + !beforeChild || beforeChildElement ? beforeChildElement : ElementTraversal::nextSibling(*beforeChild), + ContainerNode::ChildChange::Source::API, + changeType == ContainerNode::ChildChange::Type::ElementMovedInto ? ContainerNode::ChildChange::AffectsElements::Yes : ContainerNode::ChildChange::AffectsElements::No + }; +} + static ContainerNode::ChildChange NODELETE makeChildChangeForInsertion(ContainerNode& containerNode, NodeVector& children, Node* beforeChild, ContainerNode::ChildChange::Source source, ReplacedAllChildren replacedAllChildren) { using Type = ContainerNode::ChildChange::Type; @@ -1504,7 +1547,7 @@ ExceptionOr ContainerNode::moveBefore(Node& node, RefPtr&& refChild) RefPtr oldPreviousSibling = node.previousSibling(); RefPtr oldNextSibling = node.nextSibling(); - auto removalChildChange = makeChildChangeForRemoval(node, ChildChange::Source::API); + auto removalChildChange = makeChildChangeForMoveRemoval(node); { Ref nodeDocument = node.document(); @@ -1552,10 +1595,8 @@ ExceptionOr ContainerNode::moveBefore(Node& node, RefPtr&& refChild) runMovingStepsForShadowIncludingInclusiveDescendants(node, node, *oldParent, newParentIsConnected); - // FIXME: Add a new type for ChildChange. - oldParent->childrenChanged(removalChildChange); - childrenChanged(makeChildChangeForInsertion(*this, node, refChild, ChildChange::Source::API, ReplacedAllChildren::No)); + childrenChanged(makeChildChangeForMoveInsertion(*this, node, refChild)); return { }; } diff --git a/Source/WebCore/dom/ContainerNode.h b/Source/WebCore/dom/ContainerNode.h index bfee836263da..5b223f691d4a 100644 --- a/Source/WebCore/dom/ContainerNode.h +++ b/Source/WebCore/dom/ContainerNode.h @@ -83,7 +83,7 @@ class ContainerNode : public Node { enum class CanDelayNodeDeletion : uint8_t { No, Yes, Unknown }; struct ChildChange { - enum class Type : uint8_t { ElementInserted, ElementRemoved, ElementAndTextInserted, TextInserted, TextRemoved, TextChanged, AllChildrenRemoved, NonContentsChildRemoved, NonContentsChildInserted, AllChildrenReplaced }; + enum class Type : uint8_t { ElementInserted, ElementRemoved, ElementMovedInto, ElementMovedFrom, ElementAndTextInserted, TextInserted, TextRemoved, TextChanged, TextMovedFrom, TextMovedInto, AllChildrenRemoved, NonContentsChildRemoved, NonContentsChildInserted, NonContentsChildMovedInto, NonContentsChildMovedFrom, AllChildrenReplaced }; enum class Source : uint8_t { Parser, API, Clone }; enum class AffectsElements : uint8_t { Unknown, No, Yes }; @@ -107,10 +107,16 @@ class ContainerNode : public Node { case ChildChange::Type::AllChildrenReplaced: return true; case ChildChange::Type::ElementRemoved: + case ChildChange::Type::ElementMovedFrom: + case ChildChange::Type::ElementMovedInto: case ChildChange::Type::TextRemoved: + case ChildChange::Type::TextMovedFrom: + case ChildChange::Type::TextMovedInto: case ChildChange::Type::TextChanged: case ChildChange::Type::AllChildrenRemoved: case ChildChange::Type::NonContentsChildRemoved: + case ChildChange::Type::NonContentsChildMovedFrom: + case ChildChange::Type::NonContentsChildMovedInto: return false; } ASSERT_NOT_REACHED(); diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp index 7b0be72972df..f721a740d88f 100644 --- a/Source/WebCore/dom/Element.cpp +++ b/Source/WebCore/dom/Element.cpp @@ -3767,7 +3767,10 @@ void Element::childrenChanged(const ChildChange& change) switch (change.type) { case ChildChange::Type::ElementInserted: case ChildChange::Type::ElementRemoved: + case ChildChange::Type::ElementMovedFrom: + case ChildChange::Type::ElementMovedInto: // For elements, we notify shadowRoot in Element::insertionSteps and Element::removingSteps. + // FIXME(321178): Need to notify shadowRoot when elements are moved. break; case ChildChange::Type::AllChildrenRemoved: case ChildChange::Type::AllChildrenReplaced: @@ -3777,10 +3780,14 @@ void Element::childrenChanged(const ChildChange& change) case ChildChange::Type::TextInserted: case ChildChange::Type::TextRemoved: case ChildChange::Type::TextChanged: + case ChildChange::Type::TextMovedFrom: + case ChildChange::Type::TextMovedInto: shadowRoot->didMutateTextNodesOfShadowHost(); break; case ChildChange::Type::NonContentsChildInserted: case ChildChange::Type::NonContentsChildRemoved: + case ChildChange::Type::NonContentsChildMovedFrom: + case ChildChange::Type::NonContentsChildMovedInto: break; } } diff --git a/Source/WebCore/dom/ShadowRoot.cpp b/Source/WebCore/dom/ShadowRoot.cpp index 701275059c27..f2120a1387cb 100644 --- a/Source/WebCore/dom/ShadowRoot.cpp +++ b/Source/WebCore/dom/ShadowRoot.cpp @@ -173,12 +173,18 @@ void ShadowRoot::childrenChanged(const ChildChange& childChange) case ChildChange::Type::ElementRemoved: protect(m_host)->invalidateStyleForSubtree(); break; + case ChildChange::Type::ElementMovedFrom: + case ChildChange::Type::ElementMovedInto: case ChildChange::Type::TextInserted: case ChildChange::Type::TextRemoved: case ChildChange::Type::TextChanged: + case ChildChange::Type::TextMovedFrom: + case ChildChange::Type::TextMovedInto: case ChildChange::Type::AllChildrenRemoved: case ChildChange::Type::NonContentsChildRemoved: case ChildChange::Type::NonContentsChildInserted: + case ChildChange::Type::NonContentsChildMovedFrom: + case ChildChange::Type::NonContentsChildMovedInto: case ChildChange::Type::AllChildrenReplaced: break; } diff --git a/Source/WebCore/html/HTMLSelectElement.cpp b/Source/WebCore/html/HTMLSelectElement.cpp index b1046e650bf8..63f9ebfe2018 100644 --- a/Source/WebCore/html/HTMLSelectElement.cpp +++ b/Source/WebCore/html/HTMLSelectElement.cpp @@ -696,7 +696,7 @@ CompletionHandlerCallingScope HTMLSelectElement::optionToSelectFromChildChangeSc }; RefPtr optionToSelect; - if (change.type == ChildChange::Type::ElementInserted || change.type == ChildChange::Type::ElementAndTextInserted) { + if (change.type == ChildChange::Type::ElementInserted || change.type == ChildChange::Type::ElementAndTextInserted || change.type == ChildChange::Type::ElementMovedInto) { auto handleInsertedElement = [&](Element& insertedElement) { if (auto* option = dynamicDowncast(insertedElement)) { if (option->selectedWithoutUpdate()) diff --git a/Source/WebCore/svg/SVGAnimateMotionElement.cpp b/Source/WebCore/svg/SVGAnimateMotionElement.cpp index a9e8b0fb51a5..bdf5429a2e19 100644 --- a/Source/WebCore/svg/SVGAnimateMotionElement.cpp +++ b/Source/WebCore/svg/SVGAnimateMotionElement.cpp @@ -315,12 +315,18 @@ void SVGAnimateMotionElement::childrenChanged(const ChildChange& change) updateAnimationPath(); break; case ChildChange::Type::ElementInserted: + case ChildChange::Type::ElementMovedFrom: + case ChildChange::Type::ElementMovedInto: case ChildChange::Type::ElementAndTextInserted: case ChildChange::Type::TextInserted: case ChildChange::Type::TextRemoved: + case ChildChange::Type::TextMovedFrom: + case ChildChange::Type::TextMovedInto: case ChildChange::Type::TextChanged: case ChildChange::Type::NonContentsChildInserted: case ChildChange::Type::NonContentsChildRemoved: + case ChildChange::Type::NonContentsChildMovedFrom: + case ChildChange::Type::NonContentsChildMovedInto: break; } } From feb2b77758de770b74e87c730d557ed0c8ca32a8 Mon Sep 17 00:00:00 2001 From: Cole Carley Date: Fri, 28 Aug 2026 12:55:25 -0700 Subject: [PATCH 062/103] [Quirks] Remove unused function declarations https://bugs.webkit.org/show_bug.cgi?id=322687 rdar://185954750 Reviewed by Brent Fulgham. This patch removes unused function declarations from Quirks.h. * Source/WebCore/page/Quirks.h: Canonical link: https://commits.webkit.org/320070@main --- Source/WebCore/page/Quirks.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Source/WebCore/page/Quirks.h b/Source/WebCore/page/Quirks.h index 862d8d87ab84..ef34eaeb0a7c 100644 --- a/Source/WebCore/page/Quirks.h +++ b/Source/WebCore/page/Quirks.h @@ -262,7 +262,6 @@ class Quirks { bool needsToCopyUserSelectNoneQuirk() const { return m_needsToCopyUserSelectNoneQuirk; } void setNeedsToCopyUserSelectNoneQuirk() { m_needsToCopyUserSelectNoneQuirk = true; } - bool shouldEnableCanvas2DAdvancedPrivacyProtectionQuirk() const; String advancedPrivacyProtectionSubstituteDataURLForScriptWithFeatures(const String& lastDrawnText, int canvasWidth, int canvasHeight) const; bool NODELETE needsResettingTransitionCancelsRunningTransitionQuirk() const; @@ -386,13 +385,6 @@ class Quirks { private: bool needsQuirks() const; - - static bool domainNeedsAvoidResizingWhenInputViewBoundsChangeQuirk(const URL&, QuirksData&); - static bool domainNeedsScrollbarWidthThinDisabledQuirk(const URL&, QuirksData&); -#if ENABLE(VIDEO_PRESENTATION_MODE) - static bool domainShouldDisableEndFullscreenEventWhenEnteringPictureInPictureFromFullscreenQuirk(const URL&, QuirksData&); -#endif - URL topDocumentURL() const; WeakPtr m_document; From 9dcbd254af217b64f37947d680ce0c6eae0ab7e3 Mon Sep 17 00:00:00 2001 From: Keith Miller Date: Fri, 28 Aug 2026 13:09:59 -0700 Subject: [PATCH 063/103] [Wasm] Argument and Result block types should always widen https://bugs.webkit.org/show_bug.cgi?id=318807 rdar://181458746 Reviewed by Yusuke Suzuki. The wasm spec says that any types passing through a block signature have to widen to the signature. This is critical both for correctness and for security. We didn't widen in most cases, this change widens any time we enter or exit a block via fallthroughs. In the branch case we don't always widen. For br_table in particular, we have to check against each branch target, which may have different target types but the concrete value type could be a subtype of all of them. If we widened the first checked target would pass but on a second (or later) it might fail with the first's widened type. Tests: JSTests/wasm/stress/block-param-type-widening.js JSTests/wasm/stress/loop-param-type-widening.js Originally-landed-as: 305413.1112@safari-7624.5-branch (0aff244e6923). rdar://185368381 Canonical link: https://commits.webkit.org/320071@main --- .../wasm/stress/block-param-type-widening.js | 72 +++++++++ .../wasm/stress/loop-param-type-widening.js | 138 ++++++++++++++++++ .../JavaScriptCore/wasm/WasmFunctionParser.h | 105 ++++++------- 3 files changed, 253 insertions(+), 62 deletions(-) create mode 100644 JSTests/wasm/stress/block-param-type-widening.js create mode 100644 JSTests/wasm/stress/loop-param-type-widening.js diff --git a/JSTests/wasm/stress/block-param-type-widening.js b/JSTests/wasm/stress/block-param-type-widening.js new file mode 100644 index 000000000000..a7925c95b52f --- /dev/null +++ b/JSTests/wasm/stress/block-param-type-widening.js @@ -0,0 +1,72 @@ +import * as assert from "../assert.js"; + +// When a block/if/try/try_table declares a parameter type that is a *supertype* +// of the value actually on the stack, the block body must be validated against +// the DECLARED type, not the narrower concrete type that flowed in +// Each case below is built twice with an identical body: +// - declared type = anyref (wide): `struct.get 0 0` is invalid on anyref, so +// the module must be REJECTED. Before widening, the body saw the concrete +// `(ref null 0)` and this was (incorrectly) accepted. +// - declared type = (ref null 0) (concrete): `struct.get 0 0` is valid, so the +// module must VALIDATE. This control confirms the scaffolding is otherwise +// well-formed, so the rejection above is due to widening alone. + +function uleb128(n) { const r = []; do { let b = n & 0x7f; n >>>= 7; if (n) b |= 0x80; r.push(b); } while (n); return r; } +function encodeString(s) { const b = []; for (let i = 0; i < s.length; i++) b.push(s.charCodeAt(i)); return [...uleb128(b.length), ...b]; } +function section(id, content) { return [id, ...uleb128(content.length), ...content]; } + +// Module layout: +// type 0: struct { i64 mut } +// type 1: (the signature of the block under test) +// type 2: func () -> i64 (the exported function "test") +function buildModule(blockSig, body0) { + const typeSection = section(1, [ + 0x03, + 0x5F, 0x01, 0x7E, 0x01, // type 0: struct { i64 mut } + ...blockSig, // type 1 + 0x60, 0x00, 0x01, 0x7E, // type 2: () -> i64 + ]); + const funcSection = section(3, [0x01, 0x02]); // func 0 : type 2 + const exportSection = section(7, [0x01, ...encodeString("test"), 0x00, 0x00]); // export "test" func 0 + const codeSection = section(10, [0x01, ...uleb128(body0.length), ...body0]); + return new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + ...typeSection, ...funcSection, ...exportSection, ...codeSection]); +} + +const sigParam = (t) => [0x60, 0x01, ...t, 0x01, 0x7E]; // (t) -> (i64) +const ANYREF = [0x6E]; +const REF_NULL_0 = [0x63, 0x00]; // (ref null 0) + +const GET = [0xFB, 0x02, 0x00, 0x00]; // struct.get 0 0 +const REF_NULL_TYPE0 = [0xD0, 0x00]; // ref.null 0 -> (ref null 0) + +// name -> { sig: (refTypeBytes) -> typeEntry, body } +const cases = { + // (block (param T) (result i64) (struct.get 0 0)) + "block param": { + sig: sigParam, + body: [0x00, ...REF_NULL_TYPE0, 0x02, 0x01, ...GET, 0x0B, 0x0B], + }, + // (if (param T) (result i64) (then struct.get 0 0) (else drop i64.const 0)) + "if param": { + sig: sigParam, + body: [0x00, ...REF_NULL_TYPE0, 0x41, 0x01, 0x04, 0x01, ...GET, 0x05, 0x1A, 0x42, 0x00, 0x0B, 0x0B], + }, + // (try (param T) (result i64) (do struct.get 0 0) (catch_all i64.const 0)) + "try param": { + sig: sigParam, + body: [0x00, ...REF_NULL_TYPE0, 0x06, 0x01, ...GET, 0x19, 0x42, 0x00, 0x0B, 0x0B], + }, + // (try_table (param T) (result i64) (struct.get 0 0)) -- 0 catch clauses + "try_table param": { + sig: sigParam, + body: [0x00, ...REF_NULL_TYPE0, 0x1F, 0x01, 0x00, ...GET, 0x0B, 0x0B], + }, +}; + +for (const [name, { sig, body }] of Object.entries(cases)) { + assert.falsy(WebAssembly.validate(buildModule(sig(ANYREF), body)), + `${name}: struct.get on a widened anyref must be rejected (declared type not the concrete incoming type)`); + assert.truthy(WebAssembly.validate(buildModule(sig(REF_NULL_0), body)), + `${name}: struct.get on the concrete (ref null 0) must validate`); +} diff --git a/JSTests/wasm/stress/loop-param-type-widening.js b/JSTests/wasm/stress/loop-param-type-widening.js new file mode 100644 index 000000000000..0fcedc39743e --- /dev/null +++ b/JSTests/wasm/stress/loop-param-type-widening.js @@ -0,0 +1,138 @@ +function uleb128(n) { + const r = []; + do { + let b = n & 0x7f; + n >>>= 7; + if (n) b |= 0x80; + r.push(b); + } while (n); + return r; +} +function encodeString(s) { + const b = []; + for (let i = 0; i < s.length; i++) b.push(s.charCodeAt(i)); + return [...uleb128(b.length), ...b]; +} +function section(id, content) { return [id, ...uleb128(content.length), ...content]; } + +// --- Part 1 --------------------------------------------------------------- +// A loop declaring `(param anyref)` entered with a `(ref $0)` on the stack must +// typecheck its body against `anyref`, so `struct.get 0 0` at the top of the body +// is a validation error. Previously the body was typechecked against the narrower +// `(ref $0)` and the module was (unsoundly) accepted. +{ + // Type 0: struct { i64 mut } + // Type 1: func (anyref) -> (i64) — loop block signature + // Type 2: func (i32, externref, ref 0) -> (i64) + // Type 3: func () -> (ref 0) + const typeSection = section(1, [ + 0x04, + 0x5F, 0x01, 0x7E, 0x01, + 0x60, 0x01, 0x6E, 0x01, 0x7E, + 0x60, 0x03, 0x7F, 0x6F, 0x64, 0x00, 0x01, 0x7E, + 0x60, 0x00, 0x01, 0x64, 0x00, + ]); + const funcSection = section(3, [0x02, 0x02, 0x03]); + const exportSection = section(7, [ + 0x02, + ...encodeString("f"), 0x00, 0x00, + ...encodeString("make"), 0x00, 0x01, + ]); + const body0 = [ + 0x01, 0x01, 0x7E, // 1 local: i64 + 0x20, 0x02, // local.get 2 (ref $0) + 0x03, 0x01, // loop (type 1) — param anyref + 0xFB, 0x02, 0x00, 0x00, // struct.get 0 0 <-- must fail: anyref !<: (ref null $0) + 0x21, 0x03, // local.set 3 + 0x20, 0x01, // local.get 1 (externref) + 0xFB, 0x1A, // any.convert_extern + 0x20, 0x00, // local.get 0 + 0x0D, 0x00, // br_if 0 + 0x1A, // drop + 0x20, 0x03, // local.get 3 + 0x0B, // end loop + 0x0B, // end func + ]; + const body1 = [0x00, 0x42, 0x00, 0xFB, 0x00, 0x00, 0x0B]; // i64.const 0; struct.new 0 + const codeSection = section(10, [ + 0x02, + ...uleb128(body0.length), ...body0, + ...uleb128(body1.length), ...body1, + ]); + const bin = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + ...typeSection, ...funcSection, ...exportSection, ...codeSection, + ]); + + if (WebAssembly.validate(bin)) + throw new Error("Part 1: module with struct.get on anyref loop param must not validate"); +} + +// --- Part 2 --------------------------------------------------------------- +// A loop declaring `(param (ref null $0))` entered with a non-null `(ref $0)` is +// valid, but the body must be compiled against the nullable type: struct.get must +// emit its null check so a null delivered on the back-edge traps cleanly. +{ + // Type 0: struct { i64 mut } + // Type 1: func (ref null 0) -> (i64) — loop block signature + // Type 2: func (i32, ref 0) -> (i64) + // Type 3: func () -> (ref 0) + const typeSection = section(1, [ + 0x04, + 0x5F, 0x01, 0x7E, 0x01, + 0x60, 0x01, 0x63, 0x00, 0x01, 0x7E, + 0x60, 0x02, 0x7F, 0x64, 0x00, 0x01, 0x7E, + 0x60, 0x00, 0x01, 0x64, 0x00, + ]); + const funcSection = section(3, [0x02, 0x02, 0x03]); + const exportSection = section(7, [ + 0x02, + ...encodeString("f"), 0x00, 0x00, + ...encodeString("make"), 0x00, 0x01, + ]); + const body0 = [ + 0x01, 0x01, 0x7E, // 1 local: i64 + 0x20, 0x01, // local.get 1 (ref $0, non-null) + 0x03, 0x01, // loop (type 1) — param (ref null $0) + 0xFB, 0x02, 0x00, 0x00, // struct.get 0 0 <-- must keep null check + 0x21, 0x02, // local.set 2 + 0xD0, 0x71, // ref.null none + 0x20, 0x00, // local.get 0 + 0x0D, 0x00, // br_if 0 <-- back-edge with null + 0x1A, // drop + 0x20, 0x02, // local.get 2 + 0x0B, // end loop + 0x0B, // end func + ]; + const body1 = [0x00, 0x42, 0x2A, 0xFB, 0x00, 0x00, 0x0B]; // i64.const 42; struct.new 0 + const codeSection = section(10, [ + 0x02, + ...uleb128(body0.length), ...body0, + ...uleb128(body1.length), ...body1, + ]); + const bin = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + ...typeSection, ...funcSection, ...exportSection, ...codeSection, + ]); + + if (!WebAssembly.validate(bin)) + throw new Error("Part 2: module must validate"); + const inst = new WebAssembly.Instance(new WebAssembly.Module(bin)); + const s = inst.exports.make(); + + for (let i = 0; i < wasmTestLoopCount; i++) { + if (inst.exports.f(0, s) !== 42n) + throw new Error("Part 2: expected 42"); + } + + let trapped = false; + try { + inst.exports.f(1, s); + } catch (e) { + if (!(e instanceof WebAssembly.RuntimeError)) + throw new Error("Part 2: expected WebAssembly.RuntimeError, got " + e); + trapped = true; + } + if (!trapped) + throw new Error("Part 2: expected null-dereference trap on back-edge"); +} diff --git a/Source/JavaScriptCore/wasm/WasmFunctionParser.h b/Source/JavaScriptCore/wasm/WasmFunctionParser.h index 1e585656ef2d..bb18a422a6e7 100644 --- a/Source/JavaScriptCore/wasm/WasmFunctionParser.h +++ b/Source/JavaScriptCore/wasm/WasmFunctionParser.h @@ -247,13 +247,8 @@ class FunctionParser : public Parser, public FunctionParserTypes::binaryCompareCase(OpType op, BinaryOperationHandle BlockSignature inlineSignature; WASM_PARSER_FAIL_IF(!parseBlockSignatureAndNotifySIMDUseIfNeeded(inlineSignature), "can't get if's signature"_s); - const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; const uint32_t argumentCount = inlineSignature.argumentCount(); - WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few arguments on stack for if block. If expects ", argumentCount, ", but only ", sliceSize, " were present. If block has signature: ", inlineSignature); + WASM_FAIL_IF_HELPER_FAILS(checkArgumentsAndWiden(inlineSignature)); const uint32_t parentStackHeight = m_expressionStack.size() - argumentCount; - for (unsigned i = 0; i < argumentCount; ++i) - WASM_VALIDATOR_FAIL_IF(!isSubtype(m_expressionStack[parentStackHeight + i].type(), inlineSignature.argumentType(i)), "Loop expects the argument at index", i, " to be ", inlineSignature.argumentType(i), " but argument has type ", m_expressionStack[parentStackHeight + i].type()); auto args = m_expressionStack.mutableSpan().last(argumentCount); ControlType control; @@ -729,12 +721,9 @@ auto FunctionParser::unaryCompareCase(OpType op, UnaryOperationHandler BlockSignature inlineSignature; WASM_PARSER_FAIL_IF(!parseBlockSignatureAndNotifySIMDUseIfNeeded(inlineSignature), "can't get if's signature"_s); - const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; const uint32_t argumentCount = inlineSignature.argumentCount(); - WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few arguments on stack for if block. If expects ", argumentCount, ", but only ", sliceSize, " were present. If block has signature: ", inlineSignature); + WASM_FAIL_IF_HELPER_FAILS(checkArgumentsAndWiden(inlineSignature)); const uint32_t parentStackHeight = m_expressionStack.size() - argumentCount; - for (unsigned i = 0; i < argumentCount; ++i) - WASM_VALIDATOR_FAIL_IF(!isSubtype(m_expressionStack[parentStackHeight + i].type(), inlineSignature.argumentType(i)), "Loop expects the argument at index", i, " to be ", inlineSignature.argumentType(i), " but argument has type ", m_expressionStack[parentStackHeight + i].type()); auto args = m_expressionStack.mutableSpan().last(argumentCount); ControlType control; @@ -1907,21 +1896,40 @@ auto FunctionParser::checkLocalInitialized(uint32_t index) -> PartialRe } template -auto FunctionParser::checkBlockFallthrough(const ControlType& controlData, FallThroughStateTag fallthrough) -> PartialResult +auto FunctionParser::checkArgumentsAndWiden(const BlockSignature& blockSignature) -> PartialResult +{ + const uint32_t argumentCount = blockSignature.argumentCount(); + const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; + WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few values on stack for block. Block expects "_s, argumentCount, ", but only "_s, sliceSize, " were present. Block has signature: "_s, blockSignature); + const uint32_t offset = m_expressionStack.size() - argumentCount; + for (unsigned i = 0; i < argumentCount; ++i) { + auto& slot = m_expressionStack[offset + i]; + const auto expectedType = blockSignature.argumentType(i); + WASM_VALIDATOR_FAIL_IF(!isSubtype(slot.type(), expectedType), "Block expects the argument at index "_s, i, " to be "_s, expectedType, " but argument has type "_s, slot.type()); + // Widen the operand to the block's declared parameter type, per the spec's + // push_ctrl(op, in, out) doing push_vals(in): the block body must be validated + // against its declared parameter types, not the narrower subtype that flowed in. + // https://webassembly.github.io/spec/core/bikeshed/#validation-of-opcode-sequences + slot.setType(expectedType); + } + + return { }; +} + +template +auto FunctionParser::checkResultsAndWiden(const BlockSignature& blockSignature) -> PartialResult { - const auto& blockSignature = controlData.signature(); const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; WASM_VALIDATOR_FAIL_IF(blockSignature.returnCount() != sliceSize, " block with type: "_s, blockSignature, " returns: "_s, blockSignature.returnCount(), " but stack has: "_s, sliceSize, " values"_s); for (unsigned i = 0; i < blockSignature.returnCount(); ++i) { - const auto actualType = m_expressionStack[m_currentStackBegin + i].type(); + auto& slot = m_expressionStack[m_currentStackBegin + i]; const auto expectedType = blockSignature.returnType(i); - WASM_VALIDATOR_FAIL_IF(!isSubtype(actualType, expectedType), "control flow returns with unexpected type. "_s, actualType, " is not a "_s, expectedType); - // The spec requires the output type of a structured control instruction to be - // the result type from its signature, even when the fallthrough value is a subtype. - // FIXME: We should support some sort of abstract interpretation so this can be the - // least upper bound of the merging CFG. - if (fallthrough == MergePoint) - m_expressionStack[m_currentStackBegin + i].setType(expectedType); + WASM_VALIDATOR_FAIL_IF(!isSubtype(slot.type(), expectedType), "control flow returns with unexpected type. "_s, slot.type(), " is not a "_s, expectedType); + // Widen the operand to the block's declared result type, per the spec's + // end doing push_vals(frame.end_types): results leave the block as the + // declared type, not the narrower subtype that reached the end. + // https://webassembly.github.io/spec/core/bikeshed/#validation-of-opcode-sequences + slot.setType(expectedType); } return { }; @@ -1933,7 +1941,7 @@ auto FunctionParser::endBlockAndCheckResultTypes(ControlEntry& entry) - // Widen each result to the block signature type before ending the block. // FIXME: mutating the expression stack for the block result is effectful, but there's no // better API yet. See https://bugs.webkit.org/show_bug.cgi?id=164353 - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(entry.controlData, MergePoint)); + WASM_FAIL_IF_HELPER_FAILS(checkResultsAndWiden(entry.controlData.signature())); const uint32_t parentBegin = parentEntryBegin(); auto enclosedStack = m_expressionStack.mutableSpan().subspan(parentBegin); // We should avoid adding other callsites of endBlock. Since a new block is a sign of a @@ -3491,15 +3499,8 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) BlockSignature inlineSignature; WASM_PARSER_FAIL_IF(!parseBlockSignatureAndNotifySIMDUseIfNeeded(inlineSignature), "can't get block's signature"_s); - const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; const uint32_t argumentCount = inlineSignature.argumentCount(); - - WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few values on stack for block. Block expects ", argumentCount, ", but only ", sliceSize, " were present. Block has inlineSignature: ", inlineSignature); - const uint32_t parentStackHeight = m_expressionStack.size() - argumentCount; - for (unsigned i = 0; i < argumentCount; ++i) { - Type type = m_expressionStack[parentStackHeight + i].type(); - WASM_VALIDATOR_FAIL_IF(!isSubtype(type, inlineSignature.argumentType(i)), "Block expects the argument at index", i, " to be ", inlineSignature.argumentType(i), " but argument has type ", type); - } + WASM_FAIL_IF_HELPER_FAILS(checkArgumentsAndWiden(inlineSignature)); auto args = m_expressionStack.mutableSpan().last(argumentCount); ControlType block; @@ -3512,15 +3513,9 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) BlockSignature inlineSignature; WASM_PARSER_FAIL_IF(!parseBlockSignatureAndNotifySIMDUseIfNeeded(inlineSignature), "can't get loop's signature"_s); - const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; const uint32_t argumentCount = inlineSignature.argumentCount(); - - WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few values on stack for loop block. Loop expects ", argumentCount, ", but only ", sliceSize, " were present. Loop has inlineSignature: ", inlineSignature); + WASM_FAIL_IF_HELPER_FAILS(checkArgumentsAndWiden(inlineSignature)); const uint32_t parentStackHeight = m_expressionStack.size() - argumentCount; - for (unsigned i = 0; i < argumentCount; ++i) { - Type type = m_expressionStack[parentStackHeight + i].type(); - WASM_VALIDATOR_FAIL_IF(!isSubtype(type, inlineSignature.argumentType(i)), "Loop expects the argument at index", i, " to be ", inlineSignature.argumentType(i), " but argument has type ", type); - } auto args = m_expressionStack.mutableSpan().last(argumentCount); ControlType loop; @@ -3539,13 +3534,9 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) WASM_TRY_POP_EXPRESSION_STACK_INTO(condition, "if condition"_s); WASM_VALIDATOR_FAIL_IF(!condition.type().isI32(), "if condition must be i32, got ", condition.type()); - const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; const uint32_t argumentCount = inlineSignature.argumentCount(); - - WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few arguments on stack for if block. If expects ", argumentCount, ", but only ", sliceSize, " were present. If block has signature: ", inlineSignature); + WASM_FAIL_IF_HELPER_FAILS(checkArgumentsAndWiden(inlineSignature)); const uint32_t parentStackHeight = m_expressionStack.size() - argumentCount; - for (unsigned i = 0; i < argumentCount; ++i) - WASM_VALIDATOR_FAIL_IF(!isSubtype(m_expressionStack[parentStackHeight + i].type(), inlineSignature.argumentType(i)), "Loop expects the argument at index", i, " to be ", inlineSignature.argumentType(i), " but argument has type ", m_expressionStack[parentStackHeight + i].type()); auto args = m_expressionStack.mutableSpan().last(argumentCount); ControlType control; @@ -3566,7 +3557,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) ControlEntry& controlEntry = m_controlStack.last(); WASM_VALIDATOR_FAIL_IF(!ControlType::isIf(controlEntry.controlData), "else block isn't associated to an if"); - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); + WASM_FAIL_IF_HELPER_FAILS(checkResultsAndWiden(controlEntry.controlData.signature())); auto ifBranchResults = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); WASM_TRY_ADD_TO_CONTEXT(addElse(controlEntry.controlData, ifBranchResults)); m_expressionStack.shrink(m_currentStackBegin); @@ -3580,13 +3571,9 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) BlockSignature inlineSignature; WASM_PARSER_FAIL_IF(!parseBlockSignatureAndNotifySIMDUseIfNeeded(inlineSignature), "can't get try's signature"_s); - const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; const uint32_t argumentCount = inlineSignature.argumentCount(); - - WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few arguments on stack for try block. Try expects ", argumentCount, ", but only ", sliceSize, " were present. Try block has signature: ", inlineSignature); + WASM_FAIL_IF_HELPER_FAILS(checkArgumentsAndWiden(inlineSignature)); const uint32_t parentStackHeight = m_expressionStack.size() - argumentCount; - for (unsigned i = 0; i < argumentCount; ++i) - WASM_VALIDATOR_FAIL_IF(!isSubtype(m_expressionStack[parentStackHeight + i].type(), inlineSignature.argumentType(i)), "Try expects the argument at index", i, " to be ", inlineSignature.argumentType(i), " but argument has type ", m_expressionStack[parentStackHeight + i].type()); auto args = m_expressionStack.mutableSpan().last(argumentCount); ControlType control; @@ -3607,7 +3594,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) ControlEntry& controlEntry = m_controlStack.last(); WASM_VALIDATOR_FAIL_IF(!isTryOrCatch(controlEntry.controlData), "catch block isn't associated to a try"); - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); + WASM_FAIL_IF_HELPER_FAILS(checkResultsAndWiden(controlEntry.controlData.signature())); ResultList results; auto preCatchStack = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); @@ -3633,7 +3620,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) ControlEntry& controlEntry = m_controlStack.last(); WASM_VALIDATOR_FAIL_IF(!isTryOrCatch(controlEntry.controlData), "catch block isn't associated to a try"); - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(controlEntry.controlData, NewSiblingBlock)); + WASM_FAIL_IF_HELPER_FAILS(checkResultsAndWiden(controlEntry.controlData.signature())); auto preCatchStack = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); WASM_TRY_ADD_TO_CONTEXT(addCatchAll(preCatchStack, controlEntry.controlData)); @@ -3649,14 +3636,8 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) BlockSignature inlineSignature; WASM_PARSER_FAIL_IF(!parseBlockSignatureAndNotifySIMDUseIfNeeded(inlineSignature), "can't get try_table's signature"_s); - const uint32_t sliceSize = m_expressionStack.size() - m_currentStackBegin; const uint32_t argumentCount = inlineSignature.argumentCount(); - WASM_VALIDATOR_FAIL_IF(sliceSize < argumentCount, "Too few values on stack for block. Block expects ", argumentCount, ", but only ", sliceSize, " were present. Block has inlineSignature: ", inlineSignature); - const uint32_t parentStackHeight = m_expressionStack.size() - argumentCount; - for (unsigned i = 0; i < argumentCount; ++i) { - Type type = m_expressionStack[parentStackHeight + i].type(); - WASM_VALIDATOR_FAIL_IF(!isSubtype(type, inlineSignature.argumentType(i)), "Block expects the argument at index", i, " to be ", inlineSignature.argumentType(i), " but argument has type ", type); - } + WASM_FAIL_IF_HELPER_FAILS(checkArgumentsAndWiden(inlineSignature)); uint32_t numberOfCatches; Vector targets; @@ -3871,7 +3852,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) case End: { ControlEntry data = m_controlStack.takeLast(); if (ControlType::isIf(data.controlData)) { - WASM_FAIL_IF_HELPER_FAILS(checkBlockFallthrough(data.controlData, NewSiblingBlock)); + WASM_FAIL_IF_HELPER_FAILS(checkResultsAndWiden(data.controlData.signature())); auto ifBranchResults = m_expressionStack.mutableSpan().subspan(m_currentStackBegin); WASM_TRY_ADD_TO_CONTEXT(addElse(data.controlData, ifBranchResults)); m_expressionStack.shrink(m_currentStackBegin); From 26aa84fcd527016df60838ccc19c722f39b6b68a Mon Sep 17 00:00:00 2001 From: Keith Miller Date: Fri, 28 Aug 2026 13:14:23 -0700 Subject: [PATCH 064/103] OSR Availability Fails to Invalidate Local Recoveries Across LoadVarargs https://bugs.webkit.org/show_bug.cgi?id=318348 rdar://178255543 Reviewed by Yusuke Suzuki. When a strict-mode `arguments` allocation produced by an inlined varargs call is eliminated to PhantomClonedArguments, OSR availability records each argument as a promoted heap location backed by the inlined frame's stack slots. A second inlined varargs call that lowers to LoadVarargs may reuse those same virtual registers. If an OSR exit later materialises the first allocation it must not do so from those reused slots. Two independent invariants were violated: 1. LocalOSRAvailabilityCalculator::executeNode() handles PutStack/KillStack by calling killHeaps() before replacing a stack operand's availability so that any promoted heap location flushed to that operand is invalidated. The LoadVarargs / ForwardVarargs case replaced the count and argument operands without that invalidation, leaving stale promoted recoveries pointing at the overwritten slots. Apply the same killHeaps() calls before each replacement. 2. DFG arguments-elimination interference analysis must disqualify a candidate whose source-frame stack slots are clobbered while the candidate is still OSR-live. It establishes the candidate's live range using forAllKilledNodesAtNodeIndex() plus CombinedLiveness::liveAtTail. liveAtTail was computed only as the union of CFG successors' liveAtHead, each of which is pruned by bytecode liveness at the successor's first node. A candidate that is OSR-live at the block's terminal node but whose backing local is bytecode-dead at every CFG successor e.g. ``` // block 1 let a = ...; try { foo(); } catch (e) { // block 2 use(a); } // block 3 ``` Since DFG does not directly model exceptional control flow in the CFG `a` would be absent from the CFG/bytecode at block 3 and therefore absent from block 1's liveAtTail, so removeViaKill() would never be called on `a`. Note: For 2, we don't include the bytecodeLiveness for the tail of CombinedLiveness because this is both misleading with respect to how tail is used in the rest of the DFG. Additionally, it breaks ObjectAllocationSinking. ObjectAllocationSinking propagates its heap by pruning at each tail with liveAtTail then merging into successors. Since we don't prune at the head we can end up pushing phantom allocations into blocks where they don't actually dominate. Tests: JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js Originally-landed-as: 305413.1062@safari-7624.5-branch (6c004fb89bd7). rdar://185367795 Canonical link: https://commits.webkit.org/320072@main --- ...lined-load-varargs-preserves-recoveries.js | 84 ++++++++++++++++++ ...-load-varargs-kills-promoted-recoveries.js | 88 +++++++++++++++++++ .../dfg/DFGArgumentsEliminationPhase.cpp | 12 ++- .../dfg/DFGCombinedLiveness.cpp | 14 +-- .../JavaScriptCore/dfg/DFGCombinedLiveness.h | 2 + Source/JavaScriptCore/dfg/DFGForAllKills.h | 32 ------- .../dfg/DFGOSRAvailabilityAnalysisPhase.cpp | 2 + 7 files changed, 196 insertions(+), 38 deletions(-) create mode 100644 JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js create mode 100644 JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js diff --git a/JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js b/JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js new file mode 100644 index 000000000000..6a9e21ffb290 --- /dev/null +++ b/JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js @@ -0,0 +1,84 @@ +//@ runDefault("--thresholdForJITAfterWarmUp=10", "--thresholdForFTLOptimizeAfterWarmUp=1000", "--useConcurrentJIT=false", "--validateFTLOSRExitLiveness=true") + +"use strict"; + +function shouldBe(actual, expected) +{ + if (actual !== expected) + throw new Error("bad value: " + actual + ", expected: " + expected); +} + +function five(values1, values2) +{ + let result = null; + for (let i = 0; i < 5; ++i) { + function arg() { "use strict"; return arguments; } + const a = arg.apply(undefined, values1); + const b = arg.apply(undefined, values2); + try { + (3881)(b); + } catch (error) { + a.toString(); + result = a; + } + } + return result; +} + +function eight(values1, values2) +{ + let result = null; + for (let i = 0; i < 5; ++i) { + function arg() { "use strict"; return arguments; } + const a = arg.apply(undefined, values1); + const b = arg.apply(undefined, values2); + try { + (3881)(b); + } catch (error) { + a.toString(); + result = a; + } + } + return result; +} + +function filled(length, value) +{ + const result = []; + for (let i = 0; i < length; ++i) + result.push(value); + return result; +} + +const fiveMarker = { marker: "five" }; +const eightMarker = { marker: "eight" }; +const seedArray = [{ marker: "seed" }, 1, 2, 3, 4, 5]; + +const firstFive = filled(5, fiveMarker); +const overwriteFive = filled(30, fiveMarker); +overwriteFive[22] = 9; + +const firstEight = filled(8, eightMarker); +const overwriteEight = filled(30, eightMarker); +overwriteEight[20] = 9; + +for (let i = 0; i < testLoopCount; ++i) { + five(firstFive, overwriteFive); + eight(firstEight, overwriteEight); +} + +const seedValues = filled(30, seedArray); +seedValues[20] = 9; +for (let i = 0; i < testLoopCount; ++i) + eight(firstEight, seedValues); + +const recoveredEight = eight(firstEight, seedValues); +shouldBe(recoveredEight.length, firstEight.length); +for (let i = 0; i < firstEight.length; ++i) + shouldBe(recoveredEight[i], eightMarker); + +const recoveredFive = five(firstFive, overwriteFive); +shouldBe(recoveredFive.length, firstFive.length); +for (let i = 0; i < firstFive.length; ++i) + shouldBe(recoveredFive[i], fiveMarker); +shouldBe(recoveredFive[5], undefined); diff --git a/JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js b/JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js new file mode 100644 index 000000000000..aa818cb6d313 --- /dev/null +++ b/JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js @@ -0,0 +1,88 @@ +//@ runDefault("--thresholdForJITAfterWarmUp=10", "--thresholdForFTLOptimizeAfterWarmUp=1000", "--useConcurrentJIT=false") + +"use strict"; + +function shouldBe(actual, expected) +{ + if (actual !== expected) + throw new Error("bad value: " + actual + ", expected: " + expected); +} +noInline(shouldBe); + +function five(values1, values2) +{ + let result = null; + for (let i = 0; i < 5; ++i) { + function arg() { "use strict"; return arguments; } + const a = arg.apply(undefined, values1); + const b = arg.apply(undefined, values2); + try { + (3881)(b); + } catch (error) { + a.toString(); + result = a; + } + } + return result; +} +noInline(five); + +function eight(values1, values2) +{ + let result = null; + for (let i = 0; i < 5; ++i) { + function arg() { "use strict"; return arguments; } + const a = arg.apply(undefined, values1); + const b = arg.apply(undefined, values2); + try { + (3881)(b); + } catch (error) { + a.toString(); + result = a; + } + } + return result; +} +noInline(eight); + +function filled(length, value) +{ + const result = []; + for (let i = 0; i < length; ++i) + result.push(value); + return result; +} +noInline(filled); + +const fiveMarker = { marker: "five" }; +const eightMarker = { marker: "eight" }; +const seedArray = [{ marker: "seed" }, 1, 2, 3, 4, 5]; + +const firstFive = filled(5, fiveMarker); +const overwriteFive = filled(30, fiveMarker); +overwriteFive[22] = 9; + +const firstEight = filled(8, eightMarker); +const overwriteEight = filled(30, eightMarker); +overwriteEight[20] = 9; + +for (let i = 0; i < testLoopCount; ++i) { + five(firstFive, overwriteFive); + eight(firstEight, overwriteEight); +} + +const seedValues = filled(30, seedArray); +seedValues[20] = 9; +for (let i = 0; i < testLoopCount; ++i) + eight(firstEight, seedValues); + +const recoveredEight = eight(firstEight, seedValues); +shouldBe(recoveredEight.length, firstEight.length); +for (let i = 0; i < firstEight.length; ++i) + shouldBe(recoveredEight[i], eightMarker); + +const recoveredFive = five(firstFive, overwriteFive); +shouldBe(recoveredFive.length, firstFive.length); +for (let i = 0; i < firstFive.length; ++i) + shouldBe(recoveredFive[i], fiveMarker); +shouldBe(recoveredFive[5], undefined); diff --git a/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp index b90a0b9649cf..52567cea948e 100644 --- a/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp @@ -33,6 +33,7 @@ #include "DFGArgumentsUtilities.h" #include #include "DFGClobberize.h" +#include "DFGCombinedLiveness.h" #include "DFGForAllKills.h" #include "DFGGraph.h" #include "DFGInsertionSet.h" @@ -722,7 +723,16 @@ class ArgumentsEliminationPhase : public Phase { } if (clobberStack) { - for (Node* node : combinedLiveness.liveAtTail[block]) + // liveAtTail is the union of the CFG successors' liveAtHead, but a candidate can be kept + // alive solely by an exceptional exit to a catch entrypoint, which the DFG models as a + // non-CFG successor. Such a candidate is OSR-live at the terminal yet absent from + // liveAtTail, so a clobber of its source slots in this block would otherwise go + // unnoticed. Cover that gap with the nodes live at the terminal but dead on the tail. + // FIXME: If this is ever too conservative we can just calculate the locals used by + // the catch block for the terminal. + NodeSet possiblyLiveOut = bytecodeLivenessAtTerminal(m_graph, block); + possiblyLiveOut.addAll(combinedLiveness.liveAtTail[block]); + for (Node* node : possiblyLiveOut) removeViaKill(block, block->size(), node); for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { diff --git a/Source/JavaScriptCore/dfg/DFGCombinedLiveness.cpp b/Source/JavaScriptCore/dfg/DFGCombinedLiveness.cpp index b8cd7406d5aa..116bd7e9004f 100644 --- a/Source/JavaScriptCore/dfg/DFGCombinedLiveness.cpp +++ b/Source/JavaScriptCore/dfg/DFGCombinedLiveness.cpp @@ -61,6 +61,13 @@ NodeSet liveNodesAtHead(Graph& graph, BasicBlock* block) return seen; } +NodeSet bytecodeLivenessAtTerminal(Graph& graph, BasicBlock* block) +{ + NodeSet seen; + addBytecodeLiveness(graph, block->ssa->availabilityAtTail, seen, block->last()); + return seen; +} + CombinedLiveness::CombinedLiveness(Graph& graph) : liveAtHead(graph.numBlocks()) , liveAtTail(graph.numBlocks()) @@ -80,11 +87,8 @@ CombinedLiveness::CombinedLiveness(Graph& graph) // Unreachable // // And things may definitely be live in bytecode at that point in the program. - if (!block->numSuccessors()) { - NodeSet seen; - addBytecodeLiveness(graph, block->ssa->availabilityAtTail, seen, block->last()); - liveAtTail[block] = seen; - } + if (!block->numSuccessors()) + liveAtTail[block] = bytecodeLivenessAtTerminal(graph, block); } // Now compute the liveAtTail by unifying the liveAtHead of the successors. diff --git a/Source/JavaScriptCore/dfg/DFGCombinedLiveness.h b/Source/JavaScriptCore/dfg/DFGCombinedLiveness.h index a31846febbb4..4d16de38db1f 100644 --- a/Source/JavaScriptCore/dfg/DFGCombinedLiveness.h +++ b/Source/JavaScriptCore/dfg/DFGCombinedLiveness.h @@ -35,6 +35,8 @@ namespace JSC { namespace DFG { // Returns the set of nodes live at head, both due to DFG and due to bytecode (i.e. OSR exit). NodeSet liveNodesAtHead(Graph&, BasicBlock*); +NodeSet bytecodeLivenessAtTerminal(Graph&, BasicBlock*); + // WARNING: This currently does not reason about the liveness of shadow values. The execution // semantics of DFG SSA are that an Upsilon stores to the shadow value of a Phi, and the Phi loads // from that shadow value. Hence, the shadow values are like variables, and have liveness. The normal diff --git a/Source/JavaScriptCore/dfg/DFGForAllKills.h b/Source/JavaScriptCore/dfg/DFGForAllKills.h index 6cc22cb83256..8b1a134070b6 100644 --- a/Source/JavaScriptCore/dfg/DFGForAllKills.h +++ b/Source/JavaScriptCore/dfg/DFGForAllKills.h @@ -34,10 +34,6 @@ namespace JSC { namespace DFG { -namespace ForAllKillsInternal { -constexpr bool verbose = false; -} - // Utilities for finding the last points where a node is live in DFG SSA. This accounts for liveness due // to OSR exit. This is usually used for enumerating over all of the program points where a node is live, // by exploring all blocks where the node is live at tail and then exploring all program points where the @@ -170,34 +166,6 @@ void forAllKilledNodesAtNodeIndex( }); } -// Tells you all of the places to start searching from in a basic block. Gives you the node index at which -// the value is either no longer live. This pretends that nodes are dead at the end of the block, so that -// you can use this to do per-basic-block analyses. -template Functor> -void forAllKillsInBlock( - Graph& graph, const CombinedLiveness& combinedLiveness, BasicBlock* block, - const Functor& functor) -{ - for (Node* node : combinedLiveness.liveAtTail[block]) - functor(block->size(), node); - - LocalOSRAvailabilityCalculator localAvailability(graph); - localAvailability.beginBlock(block); - // Start running functor at the second node, because the functor is expected to only inspect nodes from the start of - // the block up to nodeIndex (exclusive), so if nodeIndex is zero then the functor has nothing to do. - for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { - dataLogLnIf(ForAllKillsInternal::verbose, "local availability at index: ", nodeIndex, " ", localAvailability.m_availability); - if (nodeIndex) { - forAllKilledNodesAtNodeIndex( - graph, localAvailability.m_availability, block, nodeIndex, - [&] (Node* node) { - functor(nodeIndex, node); - }); - } - localAvailability.executeNode(block->at(nodeIndex)); - } -} - } } // namespace JSC::DFG #endif // ENABLE(DFG_JIT) diff --git a/Source/JavaScriptCore/dfg/DFGOSRAvailabilityAnalysisPhase.cpp b/Source/JavaScriptCore/dfg/DFGOSRAvailabilityAnalysisPhase.cpp index 690d784b2fac..8f25e4e705c8 100644 --- a/Source/JavaScriptCore/dfg/DFGOSRAvailabilityAnalysisPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGOSRAvailabilityAnalysisPhase.cpp @@ -359,8 +359,10 @@ void LocalOSRAvailabilityCalculator::executeNode(Node* node) case LoadVarargs: case ForwardVarargs: { LoadVarargsData* data = node->loadVarargsData(); + killHeaps(data->count); m_availability.m_locals.operand(data->count) = Availability(node->child1().node(), FlushedAt(FlushedInt32, data->machineCount)); for (unsigned i = data->limit; i--;) { + killHeaps(data->start + i); m_availability.m_locals.operand(data->start + i) = Availability(FlushedAt(FlushedJSValue, data->machineStart.isValid() ? (data->machineStart + i) : VirtualRegister())); } From f0d5be8210e3d8c5ac5fd6c86e86195b64cf7bb9 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 28 Aug 2026 13:22:11 -0700 Subject: [PATCH 065/103] CanvasRenderingContext2DBase::drawTextUnchecked: don't re-use pointer returned by fontProxy() https://bugs.webkit.org/show_bug.cgi?id=318372 rdar://175759731 Reviewed by Simon Fraser. CanvasRenderingContext2D::fontProxy() returns the pointer to State::font (a FontProxy) of the top State in the state stack. State stores the FontProxy by value, so the FontProxy goes away when the State is deallocated. This could happen when the state stack (a Vector) grows beyond the storage buffer: a new buffer is created, existing State objects are copied/moved to the new buffer and the old copies deallocated. Hence, pointers returned by fontProxy() aren't safe to be re-used, because between the time when the pointer is obtained and when it's used, some operations might've manipulated the state stack and caused the FontProxy to go away. CanvasRenderingContext2DBase::drawTextUnchecked is one place where it happens: it (1) holds on to the font cascade from the FontProxy returned by fontProxy(), (2) creates a CanvasFilterContextSwitcher, whose constructor calls save() which manipulates the state stack, then (3) uses the saved font cascade from the FontProxy which might have been deallocated: void CanvasRenderingContext2DBase::drawTextUnchecked(...) { auto& fontCascade = this->fontProxy()->fontCascade(); <-- (1) [...] auto targetSwitcher = CanvasFilterContextSwitcher::create(*this, textRect); <-- (2) [...] auto drawText = [&](...) { [...] fontCascade.drawGlyphBuffer(...); <-- (3) (actually, 317546@main indirectly fixes this by avoiding saving state when creating CanvasFilterContextSwitcher. But as explained above, re-using fontCascade is unsafe, so this patch still has merits, even though it's not fixing anything) Fix this by not holding onto pointers returned by fontProxy(). Instead, whenever the FontProxy is needed, call fontProxy() so we're guaranteed to have a pointer to a live FontProxy. Additionally, FontCascade can be made CheckedPtr, so wrap it in CheckedPtr/CheckedRef whenever possible. Future patches could improve on this by making FontProxy ref-counted, so the pointer returned by fontProxy() is guaranteed to be alive no matter how the State object storing it is copied/moved around. Test: fast/canvas/canvas-filter-fillText-crash.html * LayoutTests/fast/canvas/canvas-filter-fillText-crash-expected.txt: Added. * LayoutTests/fast/canvas/canvas-filter-fillText-crash.html: Added. * Source/WebCore/SaferCPPExpectations/UncheckedLocalVarsCheckerExpectations: * Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp: (WebCore::CanvasRenderingContext2DBase::drawTextUnchecked): Originally-landed-as: 305413.1097@safari-7624.5-branch (1d58c6a24867). rdar://175759731 Canonical link: https://commits.webkit.org/320073@main --- .../canvas-filter-fillText-crash-expected.txt | 3 ++ .../canvas/canvas-filter-fillText-crash.html | 20 +++++++++++++ .../UncheckedLocalVarsCheckerExpectations | 1 - .../canvas/CanvasRenderingContext2DBase.cpp | 29 ++++++++++--------- 4 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 LayoutTests/fast/canvas/canvas-filter-fillText-crash-expected.txt create mode 100644 LayoutTests/fast/canvas/canvas-filter-fillText-crash.html diff --git a/LayoutTests/fast/canvas/canvas-filter-fillText-crash-expected.txt b/LayoutTests/fast/canvas/canvas-filter-fillText-crash-expected.txt new file mode 100644 index 000000000000..49004868ff5d --- /dev/null +++ b/LayoutTests/fast/canvas/canvas-filter-fillText-crash-expected.txt @@ -0,0 +1,3 @@ +This test passes if it does not crash. + + diff --git a/LayoutTests/fast/canvas/canvas-filter-fillText-crash.html b/LayoutTests/fast/canvas/canvas-filter-fillText-crash.html new file mode 100644 index 000000000000..6cd77d35cdbb --- /dev/null +++ b/LayoutTests/fast/canvas/canvas-filter-fillText-crash.html @@ -0,0 +1,20 @@ + + + + +

This test passes if it does not crash.

+ + + diff --git a/Source/WebCore/SaferCPPExpectations/UncheckedLocalVarsCheckerExpectations b/Source/WebCore/SaferCPPExpectations/UncheckedLocalVarsCheckerExpectations index 624acb4d0c82..55899d6a4328 100644 --- a/Source/WebCore/SaferCPPExpectations/UncheckedLocalVarsCheckerExpectations +++ b/Source/WebCore/SaferCPPExpectations/UncheckedLocalVarsCheckerExpectations @@ -10,7 +10,6 @@ dom/Position.cpp editing/Editing.cpp [ iOS ] editing/cocoa/EditorCocoa.mm [ macOS ] html/HTMLMediaElement.cpp -html/canvas/CanvasRenderingContext2DBase.cpp inspector/InspectorOverlay.cpp inspector/agents/InspectorDOMAgent.cpp layout/formattingContexts/FormattingGeometry.cpp diff --git a/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp b/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp index 69cd469fc137..6d0e83a9f9cc 100644 --- a/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp +++ b/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp @@ -2902,18 +2902,17 @@ static bool canUseCachedShapedText(const TextRun& textRun) void CanvasRenderingContext2DBase::drawTextUnchecked(const TextRun& textRun, double x, double y, bool fill, std::optional maxWidth) { - auto& fontCascade = this->fontProxy()->fontCascade(); - auto& fontMetrics = fontProxy()->metricsOfPrimaryFont(); - auto* cachedShapedText = [&]() -> TextShapingResultAndDisplayList* { if (!canUseCachedShapedText(textRun)) return nullptr; - RefPtr fonts = fontCascade.fonts(); + + CheckedRef fontCascade = fontProxy()->fontCascade(); + RefPtr fonts = fontCascade->fonts(); ASSERT(fonts); return fonts->getOrCreateCachedShapedText(textRun, fontCascade, 0, std::nullopt, ForTextEmphasis::No); }(); - float fontWidth = cachedShapedText ? cachedShapedText->textShapingResult.width : fontCascade.width(textRun); + float fontWidth = cachedShapedText ? cachedShapedText->textShapingResult.width : protect(fontProxy()->fontCascade())->width(textRun); bool useMaxWidth = maxWidth && maxWidth.value() < fontWidth; float width = useMaxWidth ? maxWidth.value() : fontWidth; @@ -2921,22 +2920,26 @@ void CanvasRenderingContext2DBase::drawTextUnchecked(const TextRun& textRun, dou location += textOffset(width, textRun.direction()); // The slop built in to this mask rect matches the heuristic used in FontCGWin.cpp for GDI text. - FloatRect textRect = FloatRect(location.x() - fontMetrics.intHeight() / 2, location.y() - fontMetrics.intAscent() - fontMetrics.intLineGap(), - width + fontMetrics.intHeight(), fontMetrics.intLineSpacing()); + FloatRect textRect = [&] () { + const auto& fontMetrics = fontProxy()->metricsOfPrimaryFont(); + return FloatRect( + location.x() - fontMetrics.intHeight() / 2, + location.y() - fontMetrics.intAscent() - fontMetrics.intLineGap(), + width + fontMetrics.intHeight(), + fontMetrics.intLineSpacing() + ); + }(); if (!fill) textRect = inflatedStrokeRect(textRect); auto targetSwitcher = CanvasFilterContextSwitcher::create(*this, textRect); - // FIXME: Need to refetch fontProxy. CanvasFilterContextSwitcher might have called save(). - // https://bugs.webkit.org/show_bug.cgi?id=193077. auto* c = effectiveDrawingContext(); - auto& fontProxy = *this->fontProxy(); bool cachedDisplayListNeedsStateSave = false; // Any shadow could add display list items to draw glyphs a 2nd time with different context attributes. if (cachedShapedText && !cachedShapedText->displayList && !cachedShapedText->textShapingResult.glyphBuffer.isEmpty() && !c->dropShadow()) { - cachedShapedText->displayList = fontCascade.displayListForGlyphBuffer(*c, cachedShapedText->textShapingResult.glyphBuffer, FontCascade::CustomFontNotReadyAction::UseFallbackIfFontNotReady); + cachedShapedText->displayList = protect(fontProxy()->fontCascade())->displayListForGlyphBuffer(*c, cachedShapedText->textShapingResult.glyphBuffer, FontCascade::CustomFontNotReadyAction::UseFallbackIfFontNotReady); if (cachedShapedText->displayList) { for (auto& item : cachedShapedText->displayList->items()) { @@ -2965,11 +2968,11 @@ void CanvasRenderingContext2DBase::drawTextUnchecked(const TextRun& textRun, dou } } else { FloatPoint startPoint = point + WebCore::size(glyphBuffer.initialAdvance()); - fontCascade.drawGlyphBuffer(context, glyphBuffer, startPoint, FontCascade::CustomFontNotReadyAction::UseFallbackIfFontNotReady); + protect(fontProxy()->fontCascade())->drawGlyphBuffer(context, glyphBuffer, startPoint, FontCascade::CustomFontNotReadyAction::UseFallbackIfFontNotReady); } } } else - fontProxy.drawBidiText(context, textRun, point, FontCascade::CustomFontNotReadyAction::UseFallbackIfFontNotReady); + fontProxy()->drawBidiText(context, textRun, point, FontCascade::CustomFontNotReadyAction::UseFallbackIfFontNotReady); }; #if USE(CG) From dd8d64894febdb0f0eab97e19fc951317f23b4cc Mon Sep 17 00:00:00 2001 From: Jessica Lee Date: Fri, 28 Aug 2026 13:32:01 -0700 Subject: [PATCH 066/103] Remove MAYBE_EVALUATE_URL_WITH_TRANSITIVE_TRUST and MAYBE_REQUEST_PERMISSION_ASK_TO macros https://bugs.webkit.org/show_bug.cgi?id=322764 rdar://179995976 Reviewed by Sihui Liu and Per Arne Vollan. These macros are no longer needed. There is a previous behavior change for WebParentalControlsURLFilter::requestPermissionForURL as we no longer fall back to using the legacy flow of requestPermissionForURL if the BrowserEngineKit API requestPermissionForURL is unavailable as we did with the macro. This was deemed acceptable as this filtering flow is only available on iOS27 and all macOS/iOS27 platforms should have the latest BrowserEngineKit API's. No new tests needed. * Source/WebKit/Shared/ios/WebParentalControlsURLFilter.mm: (WebKit::WebParentalControlsURLFilter::isURLAllowedImpl): (WebKit::WebParentalControlsURLFilter::requestPermissionForURL): Canonical link: https://commits.webkit.org/320074@main --- .../ios/WebParentalControlsURLFilter.mm | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/Source/WebKit/Shared/ios/WebParentalControlsURLFilter.mm b/Source/WebKit/Shared/ios/WebParentalControlsURLFilter.mm index 1f3239218555..3f59a8161ee6 100644 --- a/Source/WebKit/Shared/ios/WebParentalControlsURLFilter.mm +++ b/Source/WebKit/Shared/ios/WebParentalControlsURLFilter.mm @@ -94,9 +94,16 @@ RetainPtr filter = ensureWebContentFilter(); #if HAVE(WEBCONTENTRESTRICTIONS_TRANSITIVE_TRUST) #if __has_include() - if (WebCore::DeprecatedGlobalSettings::webContentRestrictionsTransitiveTrustEnabled()) { - MAYBE_EVALUATE_URL_WITH_TRANSITIVE_TRUST - } + if (WebCore::DeprecatedGlobalSettings::webContentRestrictionsTransitiveTrustEnabled()) { + BOOL isMainFrameForEvaluation = (isMainFrame == WebCore::IsMainFrameLoad::Yes); + if ([filter respondsToSelector:@selector(evaluateURL:mainFrameURL:isMainFrame:completionHandler:)]) { + [filter evaluateURL:url.createNSURL().get() mainFrameURL:mainDocumentURL.createNSURL().get() isMainFrame:isMainFrameForEvaluation completionHandler:makeBlockPtr([completionHandler = WTF::move(completionHandler)](BOOL shouldBlock, NSData *replacementData) mutable { + if (completionHandler) + completionHandler(!shouldBlock, replacementData); + }).get()]; + return; + } + } #endif #endif [filter evaluateURL:url.createNSURL().get() completionHandler:makeBlockPtr([completionHandler = WTF::move(completionHandler)](BOOL shouldBlock, NSData *replacementData) mutable { @@ -154,11 +161,43 @@ }); return; } - auto filter = ensureWebContentFilter(); + RetainPtr filter = ensureWebContentFilter(); #if __has_include() - RELEASE_LOG(Loading, "WebParentalControlsURLFilter::requestPermissionForURL starts execution"); - MAYBE_REQUEST_PERMISSION_ASK_TO + if ([filter respondsToSelector:@selector(requestPermissionForURL:referrerURL:presentingView:completionHandler:)]) { + auto permissionDecisionCompletionHandler = makeBlockPtr([completionHandler = WTF::move(completionHandler)](BEWebContentFilterPermissionDecision result, NSError *) mutable { + switch (result) { + case BEWebContentFilterPermissionDecisionError: + RELEASE_LOG(Loading, "WebParentalControlsURLFilter::requestPermissionForURL result is error"); + break; + case BEWebContentFilterPermissionDecisionAllowed: + RELEASE_LOG(Loading, "WebParentalControlsURLFilter::requestPermissionForURL result is allowed"); + break; + case BEWebContentFilterPermissionDecisionDenied: + RELEASE_LOG(Loading, "WebParentalControlsURLFilter::requestPermissionForURL result is denied"); + break; + case BEWebContentFilterPermissionDecisionPending: + RELEASE_LOG(Loading, "WebParentalControlsURLFilter::requestPermissionForURL result is pending"); + break; + default: + RELEASE_LOG_ERROR(Loading, "WebParentalControlsURLFilter::requestPermissionForURL result is invalid, result:%ld", (long)result); + break; + } + + bool didAllow = (result == BEWebContentFilterPermissionDecisionAllowed); + callOnMainRunLoop([didAllow, completionHandler = WTF::move(completionHandler)] mutable { + if (completionHandler) + completionHandler(didAllow); + }); + }); + [filter requestPermissionForURL:url.createNSURL().get() referrerURL:referrerURL.createNSURL().get() presentingView:presentingViewAsUIView completionHandler:permissionDecisionCompletionHandler.get()]; + return; + } #endif + RELEASE_LOG_ERROR(Loading, "WebParentalControlsURLFilter::requestPermissionForURL is running an unsupported configuration - default to denying permission"); + callOnMainRunLoop([completionHandler = WTF::move(completionHandler)] mutable { + if (completionHandler) + completionHandler(false); + }); }); } #endif From 1a0fe8e3886c356284628feb8e172fb91efa3052 Mon Sep 17 00:00:00 2001 From: Sean Patterson Date: Fri, 28 Aug 2026 13:37:10 -0700 Subject: [PATCH 067/103] Resync `css/css-inline/parsing` from WPT Upstream https://bugs.webkit.org/show_bug.cgi?id=322511 Reviewed by Tim Nguyen. Re-import LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing with Tools/Scripts/import-w3c-tests, and rebaseline. Upstream commit: https://github.com/web-platform-tests/wpt/commit/c782dc74987a77da4e881042fa919bf7f03d21cd 319770@main refreshed the three alignment-baseline tests in this directory; the rest of it was still stale. Five files change here, and the new assertions record two existing feature gaps rather than any change in behaviour: - baseline-shift-valid.html and baseline-shift-computed.html gained top, center and bottom, which moved onto baseline-shift in w3c/csswg-drafts#5180. WebKit does not support them: 6 new expected FAILs, tracked by bug 322465. - vertical-align-valid.html and vertical-align-computed.html were rewritten for the css-inline-3 shorthand grammar. WebKit implements only the CSS 2 grammar, so the shorthand forms, the baseline-source and alignment-baseline components reached through it, and canonical multi-value serialization all fail: 29 new expected FAILs, tracked by bug 322464. Two subtests newly pass: vertical-align-invalid.html now asserts that vertical-align: auto is invalid, which WebKit correctly rejects, and vertical-align-valid.html adds 0.5em, which WebKit accepts. One subtest goes from PASS to FAIL: vertical-align: 0. That is an assertion change, not a regression -- under the shorthand grammar 0 is equivalent to baseline and upstream now requires it to serialize canonically as "baseline", where WebKit serializes "0px". No expected FAIL lines are removed, and no subtest regresses for any other reason. The wider css/css-inline tree is deliberately out of scope: it holds 204 reftests, whose resync belongs in its own change. * LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid-expected.txt: Canonical link: https://commits.webkit.org/320075@main --- .../css/css-inline/parsing/WEB_FEATURES.yml | 6 +++ .../baseline-shift-computed-expected.txt | 3 ++ .../parsing/baseline-shift-computed.html | 3 ++ .../parsing/baseline-shift-valid-expected.txt | 3 ++ .../parsing/baseline-shift-valid.html | 3 ++ .../vertical-align-computed-expected.txt | 23 ++++++--- .../parsing/vertical-align-computed.html | 34 ++++++++++---- .../vertical-align-invalid-expected.txt | 1 + .../parsing/vertical-align-invalid.html | 3 ++ .../parsing/vertical-align-valid-expected.txt | 32 ++++++++++--- .../parsing/vertical-align-valid.html | 47 +++++++++++++++---- .../css/css-inline/parsing/w3c-import.log | 3 +- 12 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/WEB_FEATURES.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/WEB_FEATURES.yml b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/WEB_FEATURES.yml new file mode 100644 index 000000000000..a6571f3187dd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/WEB_FEATURES.yml @@ -0,0 +1,6 @@ +rules: +- alignment-baseline-*: [alignment-baseline] +- baseline-shift-*: [baseline-shift] +- dominant-baseline-*: [dominant-baseline] +- line-height-*: [line-height] +- vertical-align-*: [vertical-align] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed-expected.txt index b962f96ec6e9..1c0b2d568385 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed-expected.txt @@ -4,4 +4,7 @@ PASS Property baseline-shift value '20%' PASS Property baseline-shift value 'calc(10px - 0.5em)' PASS Property baseline-shift value 'sub' PASS Property baseline-shift value 'super' +FAIL Property baseline-shift value 'bottom' assert_true: 'bottom' is a supported value for baseline-shift. expected true got false +FAIL Property baseline-shift value 'center' assert_true: 'center' is a supported value for baseline-shift. expected true got false +FAIL Property baseline-shift value 'top' assert_true: 'top' is a supported value for baseline-shift. expected true got false diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed.html index 3bef5bae3b95..b15265a757ea 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-computed.html @@ -23,6 +23,9 @@ test_computed_value("baseline-shift", "sub"); test_computed_value("baseline-shift", "super"); +test_computed_value("baseline-shift", "bottom"); +test_computed_value("baseline-shift", "center"); +test_computed_value("baseline-shift", "top"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid-expected.txt index 0ecb0c2d9560..57e7998a62f2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid-expected.txt @@ -5,4 +5,7 @@ PASS e.style['baseline-shift'] = "calc(2em + 3ex)" should set the property value PASS e.style['baseline-shift'] = "0" should set the property value PASS e.style['baseline-shift'] = "sub" should set the property value PASS e.style['baseline-shift'] = "super" should set the property value +FAIL e.style['baseline-shift'] = "bottom" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['baseline-shift'] = "center" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['baseline-shift'] = "top" should set the property value assert_not_equals: property should be set got disallowed value "" diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid.html index 8054ebbb39cf..da663aa1a40f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/baseline-shift-valid.html @@ -17,6 +17,9 @@ test_valid_value("baseline-shift", "0", "0px"); test_valid_value("baseline-shift", "sub"); test_valid_value("baseline-shift", "super"); +test_valid_value("baseline-shift", "bottom"); +test_valid_value("baseline-shift", "center"); +test_valid_value("baseline-shift", "top"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed-expected.txt index 21a83324d9c2..e1d5272e4bb6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed-expected.txt @@ -1,14 +1,25 @@ PASS Property vertical-align value 'baseline' -PASS Property vertical-align value 'sub' -PASS Property vertical-align value 'super' -PASS Property vertical-align value 'top' -PASS Property vertical-align value 'text-top' -PASS Property vertical-align value 'middle' -PASS Property vertical-align value 'bottom' +FAIL Property vertical-align value '0' assert_equals: expected "baseline" but got "0px" +FAIL Property vertical-align value '0px' assert_equals: expected "baseline" but got "0px" +FAIL Property vertical-align value 'baseline 0' assert_true: 'baseline 0' is a supported value for vertical-align. expected true got false +FAIL Property vertical-align value '0px baseline' assert_true: '0px baseline' is a supported value for vertical-align. expected true got false +FAIL Property vertical-align value 'first' assert_true: 'first' is a supported value for vertical-align. expected true got false +FAIL Property vertical-align value 'last' assert_true: 'last' is a supported value for vertical-align. expected true got false PASS Property vertical-align value 'text-bottom' +FAIL Property vertical-align value 'alphabetic' assert_true: 'alphabetic' is a supported value for vertical-align. expected true got false +FAIL Property vertical-align value 'ideographic' assert_true: 'ideographic' is a supported value for vertical-align. expected true got false +PASS Property vertical-align value 'middle' +FAIL Property vertical-align value 'central' assert_true: 'central' is a supported value for vertical-align. expected true got false +FAIL Property vertical-align value 'mathematical' assert_true: 'mathematical' is a supported value for vertical-align. expected true got false +PASS Property vertical-align value 'text-top' PASS Property vertical-align value '-10px' PASS Property vertical-align value '20%' PASS Property vertical-align value 'calc(20% + 10px)' PASS Property vertical-align value 'calc(10px - 0.5em)' +PASS Property vertical-align value 'sub' +PASS Property vertical-align value 'super' +PASS Property vertical-align value 'top' +FAIL Property vertical-align value 'center' assert_true: 'center' is a supported value for vertical-align. expected true got false +PASS Property vertical-align value 'bottom' diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed.html index 24225084721b..61e3288ae237 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-computed.html @@ -18,20 +18,36 @@
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid-expected.txt index ddf4c43a9bed..cef1262d68b7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid-expected.txt @@ -7,4 +7,5 @@ PASS e.style['vertical-align'] = "super 10px" should not set the property value PASS e.style['vertical-align'] = "20% sub" should not set the property value PASS e.style['vertical-align'] = "baseline middle" should not set the property value PASS e.style['vertical-align'] = "text-top, bottom" should not set the property value +PASS e.style['vertical-align'] = "auto" should not set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid.html index 0565eb4a68d2..05cceb4af7ac 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-invalid.html @@ -26,6 +26,9 @@ // Two alignment-baseline values test_invalid_value("vertical-align", "baseline middle"); test_invalid_value("vertical-align", "text-top, bottom"); + +// Cannot specify `baseline-source: auto` value in shorthand +test_invalid_value("vertical-align", "auto"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid-expected.txt index b43a80243834..fa372006f521 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid-expected.txt @@ -1,14 +1,32 @@ PASS e.style['vertical-align'] = "baseline" should set the property value -PASS e.style['vertical-align'] = "sub" should set the property value -PASS e.style['vertical-align'] = "super" should set the property value -PASS e.style['vertical-align'] = "top" should set the property value -PASS e.style['vertical-align'] = "text-top" should set the property value -PASS e.style['vertical-align'] = "middle" should set the property value -PASS e.style['vertical-align'] = "bottom" should set the property value +FAIL e.style['vertical-align'] = "0" should set the property value assert_equals: serialization should be canonical expected "baseline" but got "0px" +FAIL e.style['vertical-align'] = "0px" should set the property value assert_equals: serialization should be canonical expected "baseline" but got "0px" +FAIL e.style['vertical-align'] = "baseline 0" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "0px baseline" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "first" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "last" should set the property value assert_not_equals: property should be set got disallowed value "" PASS e.style['vertical-align'] = "text-bottom" should set the property value +FAIL e.style['vertical-align'] = "alphabetic" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "ideographic" should set the property value assert_not_equals: property should be set got disallowed value "" +PASS e.style['vertical-align'] = "middle" should set the property value +FAIL e.style['vertical-align'] = "central" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "mathematical" should set the property value assert_not_equals: property should be set got disallowed value "" +PASS e.style['vertical-align'] = "text-top" should set the property value PASS e.style['vertical-align'] = "-10px" should set the property value +PASS e.style['vertical-align'] = "0.5em" should set the property value PASS e.style['vertical-align'] = "20%" should set the property value -PASS e.style['vertical-align'] = "0" should set the property value PASS e.style['vertical-align'] = "calc(20% - 10px)" should set the property value +PASS e.style['vertical-align'] = "sub" should set the property value +PASS e.style['vertical-align'] = "super" should set the property value +PASS e.style['vertical-align'] = "top" should set the property value +FAIL e.style['vertical-align'] = "center" should set the property value assert_not_equals: property should be set got disallowed value "" +PASS e.style['vertical-align'] = "bottom" should set the property value +FAIL e.style['vertical-align'] = "first baseline" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "middle 0" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "baseline super" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "last baseline sub" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "super middle first" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "1em last" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['vertical-align'] = "text-top first 10%" should set the property value assert_not_equals: property should be set got disallowed value "" diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid.html index d51c759dcc5d..d214b405cab3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/vertical-align-valid.html @@ -5,27 +5,54 @@ CSS Inline Layout: parsing vertical-align with valid values - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/w3c-import.log index e257adcd4ef2..52842df4dde0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/w3c-import.log @@ -10,10 +10,9 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: None -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: +/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/WEB_FEATURES.yml /LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/alignment-baseline-computed.html /LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/alignment-baseline-invalid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-inline/parsing/alignment-baseline-valid.html From 33a2d0520bab9ffc18aeebe94dfc166b2da966ff Mon Sep 17 00:00:00 2001 From: Yoav Weiss Date: Fri, 28 Aug 2026 14:01:19 -0700 Subject: [PATCH 068/103] Fold EarlyHintsResourceLoader into NetworkResourceLoader. https://bugs.webkit.org/show_bug.cgi?id=322828 Reviewed by Alex Christensen. The EarlyHintsResourceLoader doesn't add a ton of value. This PR folds its functionality into NetworkResourceLoader. No new tests, as this is a refactoring-only PR. * Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.cpp: Removed. * Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.h: Removed. * Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp: (WebKit::NetworkResourceLoader::didReceiveInformationalResponse): Call handleEarlyHintsResponse. (WebKit::NetworkResourceLoader::handleEarlyHintsResponse): Process EarlyHint Link headers. (WebKit::NetworkResourceLoader::constructPreconnectRequest): Create a ResourceRequest for preconnect purposes. (WebKit::NetworkResourceLoader::startPreconnectTask): Kick off a preconnect. * Source/WebKit/NetworkProcess/NetworkResourceLoader.h: * Source/WebKit/Sources.txt: * Source/WebKit/WebKit.xcodeproj/project.pbxproj: * Source/WebKit/NetworkProcess/NetworkLoad.cpp: Address safer C++ issues. (WebKit::NetworkLoad::~NetworkLoad): (WebKit::NetworkLoad::setPendingDownloadID): (WebKit::NetworkLoad::setPendingDownload): (WebKit::NetworkLoad::attributedBundleIdentifier): (WebKit::NetworkLoad::bytesTransferredOverNetwork const): Canonical link: https://commits.webkit.org/320076@main --- .../EarlyHintsResourceLoader.cpp | 160 ------------------ .../NetworkProcess/EarlyHintsResourceLoader.h | 59 ------- Source/WebKit/NetworkProcess/NetworkLoad.cpp | 2 +- .../NetworkProcess/NetworkResourceLoader.cpp | 95 ++++++++++- .../NetworkProcess/NetworkResourceLoader.h | 10 +- Source/WebKit/Sources.txt | 1 - .../WebKit/WebKit.xcodeproj/project.pbxproj | 6 - 7 files changed, 99 insertions(+), 234 deletions(-) delete mode 100644 Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.cpp delete mode 100644 Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.h diff --git a/Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.cpp b/Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.cpp deleted file mode 100644 index 10582922663c..000000000000 --- a/Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2023-2025 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" -#include "EarlyHintsResourceLoader.h" - -#include "MessageSenderInlines.h" -#include "NetworkLoadParameters.h" -#include "PreconnectTask.h" -#include "WebPageMessages.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace WebKit { -using namespace WebCore; - -WTF_MAKE_TZONE_ALLOCATED_IMPL(EarlyHintsResourceLoader); - -EarlyHintsResourceLoader::EarlyHintsResourceLoader(NetworkResourceLoader& loader) - : m_loader(&loader) -{ -} - -EarlyHintsResourceLoader::~EarlyHintsResourceLoader() = default; - -void EarlyHintsResourceLoader::addConsoleMessage(MessageSource messageSource, MessageLevel messageLevel, const String& message, unsigned long) -{ - if (!m_loader) - return; - - m_loader->send(Messages::WebPage::AddConsoleMessage { m_loader->frameID(), messageSource, messageLevel, message, m_loader->coreIdentifier() }, m_loader->pageID()); -} - -void EarlyHintsResourceLoader::enqueueSecurityPolicyViolationEvent(SecurityPolicyViolationEventInit&&) -{ -} - -void EarlyHintsResourceLoader::handleEarlyHintsResponse(ResourceResponse&& response) -{ - RELEASE_ASSERT(response.httpStatusCode() == httpStatus103EarlyHints); - - if (!m_loader) - return; - - // For consistency with other browsers, only process early hints for top-level navigation from - // secure origins using HTTP/2 or later. - if (!m_loader->isMainFrameLoad() || response.url().protocol() != "https"_s || response.httpVersion().startsWith("HTTP/1"_s)) - return; - - // Only the first early hint response served during the navigation is handled. - // FIXME: discard hints on cross-origin redirect once we support early hint preloads. - if (m_hasReceivedEarlyHints) - return; - m_hasReceivedEarlyHints = true; - - auto headerValue = response.httpHeaderField(HTTPHeaderName::Link); - if (headerValue.isEmpty()) - return; - - auto url = response.url(); - ContentSecurityPolicy contentSecurityPolicy { URL { url }, this, nullptr }; - contentSecurityPolicy.didReceiveHeaders(ContentSecurityPolicyResponseHeaders { response }, m_loader->originalRequest().httpReferrer()); - - LinkHeaderSet headerSet(headerValue); - for (const auto& header : headerSet) { - if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty() || header.isViewportDependent()) - continue; - - if (equalLettersIgnoringASCIICase(header.rel(), "preconnect"_s)) - startPreconnectTask(response.url(), header, contentSecurityPolicy); - } -} - -ResourceRequest EarlyHintsResourceLoader::constructPreconnectRequest(const ResourceRequest& originalRequest, const URL& url) -{ - ResourceRequest request { URL { url } }; - - // firstPartyForCookies and user agent are part of the HTTP socket pool keys in CFNetwork: rdar://59434166 - auto firstPartyForCookies = originalRequest.firstPartyForCookies(); - if (firstPartyForCookies.isValid()) - request.setFirstPartyForCookies(firstPartyForCookies); - - auto userAgent = originalRequest.httpUserAgent(); - if (!userAgent.isEmpty()) - request.setHTTPUserAgent(userAgent); - - return request; -} - -void EarlyHintsResourceLoader::startPreconnectTask(const URL& baseURL, const LinkHeader& header, const ContentSecurityPolicy& contentSecurityPolicy) -{ -#if ENABLE(SERVER_PRECONNECT) - RefPtr loader = m_loader.get(); - if (!loader || !loader->parameters().linkPreconnectEarlyHintsEnabled) - return; - - URL url(baseURL, header.url()); - if (!url.isValid() || url.protocol() != "https"_s) - return; - - const auto& originalRequest = loader->originalRequest(); - if (!contentSecurityPolicy.allowConnectToSource(url, { }, ContentSecurityPolicy::RedirectResponseReceived::No, originalRequest.url())) - return; - - CheckedPtr networkSession = protect(loader->connectionToWebProcess())->networkSession(); - if (!networkSession) - return; - - NetworkLoadParameters parameters; - auto globalFrameID = m_loader->globalFrameID(); - parameters.webPageProxyID = globalFrameID.webPageProxyID; - parameters.webPageID = globalFrameID.webPageID; - parameters.webFrameID = globalFrameID.frameID; - parameters.storedCredentialsPolicy = equalLettersIgnoringASCIICase(header.crossOrigin(), "anonymous"_s) ? StoredCredentialsPolicy::DoNotUse : StoredCredentialsPolicy::Use; - parameters.contentSniffingPolicy = ContentSniffingPolicy::DoNotSniffContent; - parameters.contentEncodingSniffingPolicy = ContentEncodingSniffingPolicy::Default; - parameters.shouldPreconnectOnly = PreconnectOnly::Yes; - parameters.request = constructPreconnectRequest(originalRequest, url); - parameters.isNavigatingToAppBoundDomain = m_loader->parameters().isNavigatingToAppBoundDomain; - Ref preconnectTask = PreconnectTask::create(*networkSession, WTF::move(parameters)); - preconnectTask->start(); - - addConsoleMessage(MessageSource::Network, MessageLevel::Info, makeString("Preconnecting to "_s, url.string(), " due to early hint"_s)); -#else - UNUSED_PARAM(baseURL); - UNUSED_PARAM(header); - UNUSED_PARAM(contentSecurityPolicy); -#endif -} - -} // namespace WebKit diff --git a/Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.h b/Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.h deleted file mode 100644 index 14aa69a3ca2e..000000000000 --- a/Source/WebKit/NetworkProcess/EarlyHintsResourceLoader.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2023 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "NetworkResourceLoader.h" -#include - -namespace WebCore { -class LinkHeader; -} - -namespace WebKit { - -class EarlyHintsResourceLoader - : public WebCore::ContentSecurityPolicyClient { - WTF_MAKE_TZONE_ALLOCATED(EarlyHintsResourceLoader); - WTF_MAKE_NONCOPYABLE(EarlyHintsResourceLoader); -public: - explicit EarlyHintsResourceLoader(NetworkResourceLoader&); - virtual ~EarlyHintsResourceLoader(); - - void handleEarlyHintsResponse(WebCore::ResourceResponse&&); - -private: - // ContentSecurityPolicyClient - void addConsoleMessage(MessageSource, MessageLevel, const String&, unsigned long requestIdentifier = 0) final; - void enqueueSecurityPolicyViolationEvent(WebCore::SecurityPolicyViolationEventInit&&) final; - - WebCore::ResourceRequest constructPreconnectRequest(const WebCore::ResourceRequest&, const URL&); - void startPreconnectTask(const URL& baseURL, const WebCore::LinkHeader&, const WebCore::ContentSecurityPolicy&); - - WeakPtr m_loader; - bool m_hasReceivedEarlyHints { false }; -}; - -} // namespace WebKit diff --git a/Source/WebKit/NetworkProcess/NetworkLoad.cpp b/Source/WebKit/NetworkProcess/NetworkLoad.cpp index b21c28f93f52..70fd074c5c73 100644 --- a/Source/WebKit/NetworkProcess/NetworkLoad.cpp +++ b/Source/WebKit/NetworkProcess/NetworkLoad.cpp @@ -382,7 +382,7 @@ void NetworkLoad::setTimingAllowFailedFlag() String NetworkLoad::attributedBundleIdentifier(WebPageProxyIdentifier pageID) { - if (auto* task = m_task.get()) + if (RefPtr task = m_task) return task->attributedBundleIdentifier(pageID); return { }; } diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp b/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp index 9831ee4993a5..c1334cfba500 100644 --- a/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp +++ b/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2026 Apple Inc. All rights reserved. + * Copyright (C) 2026 Shopify Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,7 +28,6 @@ #include "NetworkResourceLoader.h" #include "ArgumentCoders.h" -#include "EarlyHintsResourceLoader.h" #include "FormDataReference.h" #include "LoadedWebArchive.h" #include "Logging.h" @@ -38,6 +38,7 @@ #include "NetworkConnectionToWebProcessMessages.h" #include "NetworkLoad.h" #include "NetworkLoadChecker.h" +#include "NetworkLoadParameters.h" #include "NetworkOriginAccessPatterns.h" #include "NetworkProcess.h" #include "NetworkProcessConnectionMessages.h" @@ -45,6 +46,7 @@ #include "NetworkSchemeRegistry.h" #include "NetworkSession.h" #include "NetworkStorageManager.h" +#include "PreconnectTask.h" #include "PrivateRelayed.h" #include "QualifiedServerTrustFetch.h" #include "ResourceLoadInfo.h" @@ -79,6 +81,7 @@ #include #include #include +#include #include #include #include @@ -953,12 +956,94 @@ static BrowsingContextGroupSwitchDecision NODELETE toBrowsingContextGroupSwitchD void NetworkResourceLoader::didReceiveInformationalResponse(ResourceResponse&& response) { - if (response.httpStatusCode() != httpStatus103EarlyHints) + if (response.httpStatusCode() == httpStatus103EarlyHints) + handleEarlyHintsResponse(WTF::move(response)); +} + +void NetworkResourceLoader::handleEarlyHintsResponse(ResourceResponse&& response) +{ + // For consistency with other browsers, only process early hints for top-level navigation from + // secure origins using HTTP/2 or later. + if (!isMainFrameLoad() || response.url().protocol() != "https"_s || response.httpVersion().startsWith("HTTP/1"_s)) + return; + + // Only the first early hint response served during the navigation is handled. + // FIXME: discard hints on cross-origin redirect once we support early hint preloads. + if (m_hasReceivedEarlyHints) return; + m_hasReceivedEarlyHints = true; + + auto headerValue = response.httpHeaderField(HTTPHeaderName::Link); + if (headerValue.isEmpty()) + return; + + auto url = response.url(); + ContentSecurityPolicy contentSecurityPolicy { URL { url }, this, nullptr }; + contentSecurityPolicy.didReceiveHeaders(ContentSecurityPolicyResponseHeaders { response }, originalRequest().httpReferrer()); + + LinkHeaderSet headerSet(headerValue); + for (const auto& header : headerSet) { + if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty() || header.isViewportDependent()) + continue; + + if (equalLettersIgnoringASCIICase(header.rel(), "preconnect"_s)) + startPreconnectTask(url, header, contentSecurityPolicy); + } +} - if (!m_earlyHintsResourceLoader) - m_earlyHintsResourceLoader = WTF::makeUnique(*this); - m_earlyHintsResourceLoader->handleEarlyHintsResponse(WTF::move(response)); +ResourceRequest NetworkResourceLoader::constructPreconnectRequest(const ResourceRequest& originalRequest, const URL& url) +{ + ResourceRequest request { URL { url } }; + + // firstPartyForCookies and user agent are part of the HTTP socket pool keys in CFNetwork: rdar://59434166 + auto firstPartyForCookies = originalRequest.firstPartyForCookies(); + if (firstPartyForCookies.isValid()) + request.setFirstPartyForCookies(firstPartyForCookies); + + auto userAgent = originalRequest.httpUserAgent(); + if (!userAgent.isEmpty()) + request.setHTTPUserAgent(userAgent); + + return request; +} + +void NetworkResourceLoader::startPreconnectTask(const URL& baseURL, const LinkHeader& header, const ContentSecurityPolicy& contentSecurityPolicy) +{ +#if ENABLE(SERVER_PRECONNECT) + if (!parameters().linkPreconnectEarlyHintsEnabled) + return; + + URL url(baseURL, header.url()); + if (!url.isValid() || url.protocol() != "https"_s) + return; + + if (!contentSecurityPolicy.allowConnectToSource(url, { }, ContentSecurityPolicy::RedirectResponseReceived::No, originalRequest().url())) + return; + + CheckedPtr networkSession = protect(connectionToWebProcess())->networkSession(); + if (!networkSession) + return; + + NetworkLoadParameters parameters; + auto globalFrameID = this->globalFrameID(); + parameters.webPageProxyID = globalFrameID.webPageProxyID; + parameters.webPageID = globalFrameID.webPageID; + parameters.webFrameID = globalFrameID.frameID; + parameters.storedCredentialsPolicy = equalLettersIgnoringASCIICase(header.crossOrigin(), "anonymous"_s) ? StoredCredentialsPolicy::DoNotUse : StoredCredentialsPolicy::Use; + parameters.contentSniffingPolicy = ContentSniffingPolicy::DoNotSniffContent; + parameters.contentEncodingSniffingPolicy = ContentEncodingSniffingPolicy::Default; + parameters.shouldPreconnectOnly = PreconnectOnly::Yes; + parameters.request = constructPreconnectRequest(originalRequest(), url); + parameters.isNavigatingToAppBoundDomain = this->parameters().isNavigatingToAppBoundDomain; + Ref preconnectTask = PreconnectTask::create(*networkSession, WTF::move(parameters)); + preconnectTask->start(); + + addConsoleMessage(MessageSource::Network, MessageLevel::Info, makeString("Preconnecting to "_s, url.string(), " due to early hint"_s)); +#else + UNUSED_PARAM(baseURL); + UNUSED_PARAM(header); + UNUSED_PARAM(contentSecurityPolicy); +#endif } void NetworkResourceLoader::didReceiveResponse(ResourceResponse&& receivedResponse, PrivateRelayed privateRelayed, ResponseCompletionHandler&& completionHandler) diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoader.h b/Source/WebKit/NetworkProcess/NetworkResourceLoader.h index 225f8e00ac3f..50b5410d918b 100644 --- a/Source/WebKit/NetworkProcess/NetworkResourceLoader.h +++ b/Source/WebKit/NetworkProcess/NetworkResourceLoader.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2026 Apple Inc. All rights reserved. + * Copyright (C) 2026 Shopify 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 +60,7 @@ class SharedBufferReference; namespace WebCore { class BlobDataFileReference; class ContentFilter; +class ContentSecurityPolicy; class FormData; class LinkHeader; class NetworkStorageSession; @@ -69,7 +71,6 @@ class ResourceRequest; namespace WebKit { -class EarlyHintsResourceLoader; class NetworkConnectionToWebProcess; class NetworkLoad; class NetworkLoadChecker; @@ -293,6 +294,11 @@ class NetworkResourceLoader final void addConsoleMessage(MessageSource, MessageLevel, const String&, unsigned long requestIdentifier = 0) final; void enqueueSecurityPolicyViolationEvent(WebCore::SecurityPolicyViolationEventInit&&) final; + // HTTP 103 Early Hints. + void handleEarlyHintsResponse(WebCore::ResourceResponse&&); + WebCore::ResourceRequest constructPreconnectRequest(const WebCore::ResourceRequest&, const URL&); + void startPreconnectTask(const URL& baseURL, const WebCore::LinkHeader&, const WebCore::ContentSecurityPolicy&); + void logSlowCacheRetrieveIfNeeded(const NetworkCache::Cache::RetrieveInfo&); std::optional validateCacheEntryForMaxAgeCapValidation(const WebCore::ResourceRequest&, const WebCore::ResourceRequest& redirectRequest, const WebCore::ResourceResponse&); @@ -398,7 +404,7 @@ class NetworkResourceLoader final bool m_shouldCaptureExtraNetworkLoadMetrics { false }; bool m_isKeptAlive { false }; - std::unique_ptr m_earlyHintsResourceLoader; + bool m_hasReceivedEarlyHints { false }; std::optional m_networkActivityTracker; RefPtr m_serviceWorkerFetchTask; diff --git a/Source/WebKit/Sources.txt b/Source/WebKit/Sources.txt index 1c12dcfb8070..1122067045df 100644 --- a/Source/WebKit/Sources.txt +++ b/Source/WebKit/Sources.txt @@ -104,7 +104,6 @@ ModelProcess/ModelProcessModelPlayerManagerProxy.cpp NetworkProcess/BackgroundFetchLoad.cpp @cost:4 NetworkProcess/DatabaseUtilities.cpp @cost:3 -NetworkProcess/EarlyHintsResourceLoader.cpp @cost:6 NetworkProcess/NetworkActivityTracker.cpp NetworkProcess/NetworkBroadcastChannelRegistry.cpp @cost:4 NetworkProcess/NetworkCORSPreflightChecker.cpp @cost:4 diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj index 4347c1a3633c..98faf1f6e8da 100644 --- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj +++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj @@ -2568,7 +2568,6 @@ EB44A69C2C8A4F6E006595BF /* WebClipCache.h in Headers */ = {isa = PBXBuildFile; fileRef = EB44A6992C8A4F6E006595BF /* WebClipCache.h */; }; EB450E0F2996C7B6009724B1 /* WKWebsiteDataStoreRefPrivateMac.h in Headers */ = {isa = PBXBuildFile; fileRef = EB450E0D2996C7A1009724B1 /* WKWebsiteDataStoreRefPrivateMac.h */; settings = {ATTRIBUTES = (Private, ); }; }; EB517B802D7C09690019E451 /* AboutSchemeHandlerCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = EB517B7A2D7BC03B0019E451 /* AboutSchemeHandlerCocoa.mm */; }; - EB579C3729AEBD0800894C1C /* EarlyHintsResourceLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = EB579C3629AEBCF100894C1C /* EarlyHintsResourceLoader.h */; }; EB7D252B27B31B77009CB586 /* com.apple.WebKit.webpushd.mac.sb in Resources */ = {isa = PBXBuildFile; fileRef = EB7D252A27B31B3F009CB586 /* com.apple.WebKit.webpushd.mac.sb */; }; EB8322162D72600C009515DA /* AboutSchemeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = EB8322132D72600C009515DA /* AboutSchemeHandler.h */; }; EBA8D3AB27A5E31300CB7900 /* ApplePushServiceSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = EBA8D3AA27A5E31300CB7900 /* ApplePushServiceSPI.h */; }; @@ -8908,8 +8907,6 @@ EB450E0D2996C7A1009724B1 /* WKWebsiteDataStoreRefPrivateMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WKWebsiteDataStoreRefPrivateMac.h; path = mac/WKWebsiteDataStoreRefPrivateMac.h; sourceTree = ""; }; EB450E0E2996C7A1009724B1 /* WKWebsiteDataStoreRefPrivateMac.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = WKWebsiteDataStoreRefPrivateMac.mm; path = mac/WKWebsiteDataStoreRefPrivateMac.mm; sourceTree = ""; }; EB517B7A2D7BC03B0019E451 /* AboutSchemeHandlerCocoa.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AboutSchemeHandlerCocoa.mm; sourceTree = ""; }; - EB579C3529AEBCF100894C1C /* EarlyHintsResourceLoader.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EarlyHintsResourceLoader.cpp; sourceTree = ""; }; - EB579C3629AEBCF100894C1C /* EarlyHintsResourceLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EarlyHintsResourceLoader.h; sourceTree = ""; }; EB7D252927B316A6009CB586 /* com.apple.WebKit.webpushd.mac.sb.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = com.apple.WebKit.webpushd.mac.sb.in; sourceTree = ""; }; EB7D252A27B31B3F009CB586 /* com.apple.WebKit.webpushd.mac.sb */ = {isa = PBXFileReference; lastKnownFileType = file; path = com.apple.WebKit.webpushd.mac.sb; sourceTree = ""; }; EB8322132D72600C009515DA /* AboutSchemeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutSchemeHandler.h; sourceTree = ""; }; @@ -14272,8 +14269,6 @@ 41E4C3F429A3E38800EA6B3A /* BackgroundFetchLoad.h */, 5C826D6E26D58A16008AEC91 /* DatabaseUtilities.cpp */, 5C826D6F26D58A16008AEC91 /* DatabaseUtilities.h */, - EB579C3529AEBCF100894C1C /* EarlyHintsResourceLoader.cpp */, - EB579C3629AEBCF100894C1C /* EarlyHintsResourceLoader.h */, 53F3CAA5206C443E0086490E /* NetworkActivityTracker.cpp */, 535BCB902069C49C00CCCE02 /* NetworkActivityTracker.h */, 5C3B0A012DA0000000ABCDEF /* NetworkActivityTracker.serialization.in */, @@ -18438,7 +18433,6 @@ 2D50F88F2B21C5170042E139 /* DynamicContentScalingBifurcatedImageBuffer.h in Headers */, BCE579A62634836700F5C5E9 /* DynamicContentScalingImageBufferBackend.h in Headers */, 2DA6731A20C754B1003CB401 /* DynamicViewportSizeUpdate.h in Headers */, - EB579C3729AEBD0800894C1C /* EarlyHintsResourceLoader.h in Headers */, E105FE5418D7B9DE008F57A8 /* EditingRange.h in Headers */, 1AA41AB512C02EC4002BE67B /* EditorState.h in Headers */, DD4DB786280F945A001700D4 /* ElementAttribute.js in Headers */, From bc7a9e0619dd06c2f3332aaf5ca1ea1e612640d2 Mon Sep 17 00:00:00 2001 From: Ian Gower Date: Fri, 28 Aug 2026 14:16:50 -0700 Subject: [PATCH 069/103] Persist the resolved IP address space with a cached response https://bugs.webkit.org/show_bug.cgi?id=322661 rdar://185933660 Reviewed by Chris Dumez. Part of the Local Network Access implementation, https://wicg.github.io/local-network-access/ - the check compares the address space a connection resolved to against the client's, so that space has to survive being written to the network cache. ResourceResponse carries the address space the connection resolved to, but it was dropped on the way to disk: neither persistence coder encoded it, and the ResourceResponseData decoder hardcoded IPAddressSpace::Unknown. A response read back from the network cache therefore lost the space its connection had originally resolved to. Both coders now encode and decode it, and EnumTraitsForPersistence is declared for IPAddressSpace so the enum can be persisted at all. The cache version is bumped, since existing entries were written without the field and cannot be read back with it. Cache::update() takes the space from the validating response rather than keeping the stored one. The body is reused on a 304, but the resource is being served now from the validating connection, and a host that has since moved to a local address must not be reachable on the address space it used to have. Two tests cover the coders separately, across all four enum values. Reverting either coder fails only its own test. * Source/WebCore/platform/WebCorePersistentCoders.cpp: (WTF::Persistence::Coder::encodeForPersistence): (WTF::Persistence::Coder::decodeForPersistence): * Source/WebCore/platform/network/ResourceResponseBase.cpp: (WTF::Persistence::Coder::encodeForPersistence): (WTF::Persistence::Coder::decodeForPersistence): * Source/WebCore/platform/network/ResourceResponseBase.h: * Source/WebKit/NetworkProcess/cache/NetworkCache.cpp: (WebKit::NetworkCache::Cache::update): * Source/WebKit/NetworkProcess/cache/NetworkCacheStorage.h: * Tools/TestWebKitAPI/Tests/WebCore/IPAddressSpaceTests.cpp: Canonical link: https://commits.webkit.org/320077@main --- .../platform/WebCorePersistentCoders.cpp | 7 +++ .../platform/network/ResourceResponseBase.cpp | 8 +++- .../platform/network/ResourceResponseBase.h | 10 +++++ .../NetworkProcess/cache/NetworkCache.cpp | 1 + .../cache/NetworkCacheStorage.h | 2 +- .../Tests/WebCore/IPAddressSpaceTests.cpp | 43 +++++++++++++++++++ 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Source/WebCore/platform/WebCorePersistentCoders.cpp b/Source/WebCore/platform/WebCorePersistentCoders.cpp index 25a63f5f3498..4a8304a73c68 100644 --- a/Source/WebCore/platform/WebCorePersistentCoders.cpp +++ b/Source/WebCore/platform/WebCorePersistentCoders.cpp @@ -699,6 +699,7 @@ void Coder::encodeForPersistence(Encoder& encoder, co WebCore::WasPrivateRelayed wasPrivateRelayed = instance.m_wasPrivateRelayed; encoder << wasPrivateRelayed; encoder << instance.m_isRangeRequested; + encoder << static_cast(instance.m_ipAddressSpace); } std::optional Coder::decodeForPersistence(Decoder& decoder) @@ -810,6 +811,12 @@ std::optional Coder::decod return std::nullopt; response.m_isRangeRequested = WTF::move(*isRangeRequested); + std::optional ipAddressSpace; + decoder >> ipAddressSpace; + if (!ipAddressSpace) + return std::nullopt; + response.m_ipAddressSpace = WTF::move(*ipAddressSpace); + return { WTF::move(response) }; } diff --git a/Source/WebCore/platform/network/ResourceResponseBase.cpp b/Source/WebCore/platform/network/ResourceResponseBase.cpp index 4a7da70e5fe4..0725319f8a3a 100644 --- a/Source/WebCore/platform/network/ResourceResponseBase.cpp +++ b/Source/WebCore/platform/network/ResourceResponseBase.cpp @@ -950,6 +950,7 @@ void Coder::encodeForPersistence(Encoder& encoder encoder << data.wasPrivateRelayed; encoder << data.proxyName; encoder << data.isRangeRequested; + encoder << data.ipAddressSpace; } std::optional Coder::decodeForPersistence(Decoder& decoder) @@ -1039,6 +1040,11 @@ std::optional Coder ipAddressSpace; + decoder >> ipAddressSpace; + if (!ipAddressSpace) + return std::nullopt; + return WebCore::ResourceResponseData { WTF::move(*url), WTF::move(*mimeType), @@ -1058,7 +1064,7 @@ std::optional Coder struct EnumTraitsForPersistence; }; +template<> struct EnumTraitsForPersistence { + using values = EnumValues< + WebCore::IPAddressSpace, + WebCore::IPAddressSpace::Public, + WebCore::IPAddressSpace::Local, + WebCore::IPAddressSpace::Loopback, + WebCore::IPAddressSpace::Unknown + >; +}; + namespace Persistence { class Decoder; diff --git a/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp b/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp index 8cd0098870c2..bf0d6c9fb81f 100644 --- a/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp +++ b/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp @@ -614,6 +614,7 @@ std::unique_ptr Cache::update(const WebCore::ResourceRequest& originalReq WebCore::ResourceResponse response = existingEntry.response(); WebCore::updateResponseHeadersAfterRevalidation(response, validatingResponse); + response.setIPAddressSpace(validatingResponse.ipAddressSpace()); auto updateEntry = makeUnique(existingEntry.key(), response, privateRelayed, existingEntry.buffer(), WebCore::collectVaryingRequestHeaders(protect(m_networkProcess->storageSession(m_sessionID)), originalRequest, response)); auto updateRecord = updateEntry->encodeAsStorageRecord(); diff --git a/Source/WebKit/NetworkProcess/cache/NetworkCacheStorage.h b/Source/WebKit/NetworkProcess/cache/NetworkCacheStorage.h index 1a5c9fa10b6f..94ebb4052823 100644 --- a/Source/WebKit/NetworkProcess/cache/NetworkCacheStorage.h +++ b/Source/WebKit/NetworkProcess/cache/NetworkCacheStorage.h @@ -131,7 +131,7 @@ class Storage : public ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr #include #include +#include #include +#include +#include +#include namespace TestWebKitAPI { @@ -459,4 +463,43 @@ TEST(IPAddressSpace, DoesNotOvermatchReservedNames) EXPECT_EQ(WebCore::determineIPAddressSpace(URL("https://local.example.com/"_s)), WebCore::IPAddressSpace::Public); } +// Both persistence coders are covered: ResourceResponseData is what the network cache stores, and +// ResourceResponse is what other persistent callers encode. +TEST(IPAddressSpace, SurvivesResponseDataPersistenceRoundTrip) +{ + for (auto space : { WebCore::IPAddressSpace::Public, WebCore::IPAddressSpace::Local, WebCore::IPAddressSpace::Loopback, WebCore::IPAddressSpace::Unknown }) { + WebCore::ResourceResponse response { URL { "http://192.168.1.1/"_s }, "text/plain"_s, 5, "UTF-8"_s }; + response.setIPAddressSpace(space); + + auto data = response.getResponseData(); + ASSERT_TRUE(data.has_value()); + + WTF::Persistence::Encoder encoder; + WTF::Persistence::Coder::encodeForPersistence(encoder, *data); + + WTF::Persistence::Decoder decoder(encoder.span()); + auto decoded = WTF::Persistence::Coder::decodeForPersistence(decoder); + ASSERT_TRUE(decoded.has_value()); + + EXPECT_EQ(decoded->ipAddressSpace, space); + } +} + +TEST(IPAddressSpace, SurvivesResourceResponsePersistenceRoundTrip) +{ + for (auto space : { WebCore::IPAddressSpace::Public, WebCore::IPAddressSpace::Local, WebCore::IPAddressSpace::Loopback, WebCore::IPAddressSpace::Unknown }) { + WebCore::ResourceResponse response { URL { "http://192.168.1.1/"_s }, "text/plain"_s, 5, "UTF-8"_s }; + response.setIPAddressSpace(space); + + WTF::Persistence::Encoder encoder; + WTF::Persistence::Coder::encodeForPersistence(encoder, response); + + WTF::Persistence::Decoder decoder(encoder.span()); + auto decoded = WTF::Persistence::Coder::decodeForPersistence(decoder); + ASSERT_TRUE(decoded.has_value()); + + EXPECT_EQ(decoded->ipAddressSpace(), space); + } +} + } From 084154b27a5c5184b5cb401c9b6916d71a4d120d Mon Sep 17 00:00:00 2001 From: Sam Weinig Date: Fri, 28 Aug 2026 14:18:12 -0700 Subject: [PATCH 070/103] Unify naming of zoom applying/unapplying functions https://bugs.webkit.org/show_bug.cgi?id=322599 Reviewed by Darin Adler and Taher Ali. Unify naming of zoom applying/unapplying functions to: - applyingZoom(...) - unapplyingZoom(...) Making the type required makes it more clear at call sites what conversions are happening. The previous behavior of truncating to int when using the base function was unclear. * Source/WebCore/css/query/ContainerQueryFeatures.cpp: * Source/WebCore/css/query/MediaQueryFeatures.cpp: * Source/WebCore/dom/Document.cpp: * Source/WebCore/dom/Element.cpp: * Source/WebCore/dom/ImageOverlay.cpp: * Source/WebCore/dom/ViewTransition.cpp: * Source/WebCore/html/HTMLImageElement.cpp: * Source/WebCore/html/ImageInputType.cpp: * Source/WebCore/inspector/InspectorOverlay.cpp: * Source/WebCore/page/ResizeObservation.cpp: * Source/WebCore/style/StyleExtractor.cpp: * Source/WebCore/style/values/viewport/StyleZoomPrimitives.h: * Source/WebCore/style/values/viewport/StyleZoomPrimitivesInlines.h: Canonical link: https://commits.webkit.org/320078@main --- .../css/query/ContainerQueryFeatures.cpp | 8 +- .../WebCore/css/query/MediaQueryFeatures.cpp | 4 +- Source/WebCore/dom/Document.cpp | 4 +- Source/WebCore/dom/Element.cpp | 28 +++---- Source/WebCore/dom/ImageOverlay.cpp | 4 +- Source/WebCore/dom/ViewTransition.cpp | 2 +- Source/WebCore/html/HTMLImageElement.cpp | 4 +- Source/WebCore/html/ImageInputType.cpp | 4 +- Source/WebCore/inspector/InspectorOverlay.cpp | 4 +- Source/WebCore/page/ResizeObservation.cpp | 6 +- Source/WebCore/style/StyleExtractor.cpp | 2 +- .../values/viewport/StyleZoomPrimitives.h | 29 ++++--- .../viewport/StyleZoomPrimitivesInlines.h | 75 ++++++++----------- 13 files changed, 79 insertions(+), 95 deletions(-) diff --git a/Source/WebCore/css/query/ContainerQueryFeatures.cpp b/Source/WebCore/css/query/ContainerQueryFeatures.cpp index 7130f7ea5677..b776cd134e00 100644 --- a/Source/WebCore/css/query/ContainerQueryFeatures.cpp +++ b/Source/WebCore/css/query/ContainerQueryFeatures.cpp @@ -96,7 +96,7 @@ struct WidthFeatureSchema : public SizeFeatureSchema { EvaluationResult evaluate(const MQ::Feature& feature, const RenderBox& renderer, const CSSToLengthConversionData& conversionData) const override { - auto width = Style::adjustForAbsoluteZoom(renderer.contentBoxWidth(), renderer); + auto width = Style::unapplyingZoom(renderer.contentBoxWidth(), renderer); return evaluateLengthFeature(feature, width, conversionData); } }; @@ -111,7 +111,7 @@ struct HeightFeatureSchema : public SizeFeatureSchema { EvaluationResult evaluate(const MQ::Feature& feature, const RenderBox& renderer, const CSSToLengthConversionData& conversionData) const override { - auto height = Style::adjustForAbsoluteZoom(renderer.contentBoxHeight(), renderer); + auto height = Style::unapplyingZoom(renderer.contentBoxHeight(), renderer); return evaluateLengthFeature(feature, height, conversionData); } }; @@ -126,7 +126,7 @@ struct InlineSizeFeatureSchema : public SizeFeatureSchema { EvaluationResult evaluate(const MQ::Feature& feature, const RenderBox& renderer, const CSSToLengthConversionData& conversionData) const override { - auto logicalWidth = Style::adjustForAbsoluteZoom(renderer.contentBoxLogicalWidth(), renderer); + auto logicalWidth = Style::unapplyingZoom(renderer.contentBoxLogicalWidth(), renderer); return evaluateLengthFeature(feature, logicalWidth, conversionData); } }; @@ -141,7 +141,7 @@ struct BlockSizeFeatureSchema : public SizeFeatureSchema { EvaluationResult evaluate(const MQ::Feature& feature, const RenderBox& renderer, const CSSToLengthConversionData& conversionData) const override { - auto logicalHeight = Style::adjustForAbsoluteZoom(renderer.contentBoxLogicalHeight(), renderer); + auto logicalHeight = Style::unapplyingZoom(renderer.contentBoxLogicalHeight(), renderer); return evaluateLengthFeature(feature, logicalHeight, conversionData); } }; diff --git a/Source/WebCore/css/query/MediaQueryFeatures.cpp b/Source/WebCore/css/query/MediaQueryFeatures.cpp index dd3d322ac58e..c2014ca6eb8e 100644 --- a/Source/WebCore/css/query/MediaQueryFeatures.cpp +++ b/Source/WebCore/css/query/MediaQueryFeatures.cpp @@ -429,7 +429,7 @@ static const LengthSchema& heightFeatureSchema() [](auto& context) { auto height = protect(context.document->view())->layoutHeight(); if (CheckedPtr renderView = context.document->renderView()) - height = Style::adjustForAbsoluteZoom(height, *renderView); + height = Style::unapplyingZoom(height, *renderView); return height; } }; @@ -729,7 +729,7 @@ static const LengthSchema& widthFeatureSchema() [](auto& context) { auto width = protect(context.document->view())->layoutWidth(); if (CheckedPtr renderView = context.document->renderView()) - width = Style::adjustForAbsoluteZoom(width, *renderView); + width = Style::unapplyingZoom(width, *renderView); return width; } }; diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp index c65ca919bb8e..4099202f72ed 100644 --- a/Source/WebCore/dom/Document.cpp +++ b/Source/WebCore/dom/Document.cpp @@ -535,12 +535,12 @@ static void CallbackForContainIntrinsicSize(const VectorcontentBoxSize().at(0); if (box->style().logicalContainIntrinsicWidth().hasAuto()) { - auto adjustedWidth = LayoutUnit { Style::applyZoom(contentBoxSize->inlineSize(), box->style()) }; + auto adjustedWidth = LayoutUnit { Style::applyingZoom(contentBoxSize->inlineSize(), box->style()) }; target->setLastRememberedLogicalWidth(adjustedWidth); } if (box->style().logicalContainIntrinsicHeight().hasAuto()) { - auto adjustedHeight = LayoutUnit { Style::applyZoom(contentBoxSize->blockSize(), box->style()) }; + auto adjustedHeight = LayoutUnit { Style::applyingZoom(contentBoxSize->blockSize(), box->style()) }; target->setLastRememberedLogicalHeight(adjustedHeight); } } diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp index f721a740d88f..06c9322cff96 100644 --- a/Source/WebCore/dom/Element.cpp +++ b/Source/WebCore/dom/Element.cpp @@ -1447,8 +1447,8 @@ void Element::scrollTo(const ScrollToOptions& options, ScrollClamping clamping, return; auto scrollToOptions = normalizeNonFiniteCoordinatesOrFallBackTo(options, - Style::adjustForAbsoluteZoom(renderer->scrollLeft(), *renderer), - Style::adjustForAbsoluteZoom(renderer->scrollTop(), *renderer) + Style::unapplyingZoom(renderer->scrollLeft(), *renderer), + Style::unapplyingZoom(renderer->scrollTop(), *renderer) ); IntPoint scrollPosition( clampTo(scrollToOptions.left.value() * renderer->style().usedZoom()), @@ -1590,7 +1590,7 @@ int Element::offsetWidth() protect(document())->updateLayoutIfDimensionsOutOfDate(*this, DimensionsCheck::Width, { LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible, LayoutOptions::IgnorePendingStylesheets }); if (CheckedPtr renderer = renderBoxModelObject()) { auto offsetWidth = LayoutUnit { roundToInt(renderer->offsetWidth()) }; - return convertToNonSubpixelValue(Style::adjustLayoutUnitForAbsoluteZoom(offsetWidth, *renderer).toDouble()); + return convertToNonSubpixelValue(Style::unapplyingZoom(offsetWidth, *renderer).toDouble()); } return 0; } @@ -1600,7 +1600,7 @@ int Element::offsetHeight() protect(document())->updateLayoutIfDimensionsOutOfDate(*this, DimensionsCheck::Height, { LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible, LayoutOptions::IgnorePendingStylesheets }); if (CheckedPtr renderer = renderBoxModelObject()) { auto offsetHeight = LayoutUnit { roundToInt(renderer->offsetHeight()) }; - return convertToNonSubpixelValue(Style::adjustLayoutUnitForAbsoluteZoom(offsetHeight, *renderer).toDouble()); + return convertToNonSubpixelValue(Style::unapplyingZoom(offsetHeight, *renderer).toDouble()); } return 0; } @@ -1631,7 +1631,7 @@ int Element::clientLeft() if (CheckedPtr renderer = renderBox()) { auto clientLeft = LayoutUnit { roundToInt(renderer->borderLeft()) }; - return convertToNonSubpixelValue(Style::adjustLayoutUnitForAbsoluteZoom(clientLeft, *renderer).toDouble()); + return convertToNonSubpixelValue(Style::unapplyingZoom(clientLeft, *renderer).toDouble()); } return 0; } @@ -1642,7 +1642,7 @@ int Element::clientTop() if (CheckedPtr renderer = renderBox()) { auto clientTop = LayoutUnit { roundToInt(renderer->borderTop()) }; - return convertToNonSubpixelValue(Style::adjustLayoutUnitForAbsoluteZoom(clientTop, *renderer).toDouble()); + return convertToNonSubpixelValue(Style::unapplyingZoom(clientTop, *renderer).toDouble()); } return 0; } @@ -1661,7 +1661,7 @@ int Element::clientWidth() // When in quirks mode, clientWidth for the body element should return the width of the containing frame. bool inQuirksMode = document->inQuirksMode(); if ((!inQuirksMode && document->documentElement() == this) || (inQuirksMode && isHTMLElement() && document->bodyOrFrameset() == this)) - return Style::adjustForAbsoluteZoom(protect(renderView->frameView())->layoutWidth(), renderView); + return Style::unapplyingZoom(protect(renderView->frameView())->layoutWidth(), renderView); if (CheckedPtr renderer = renderBox()) { auto clientWidth = LayoutUnit { roundToInt(renderer->paddingBoxWidth()) }; @@ -1680,7 +1680,7 @@ int Element::clientWidth() clientWidth += renderer->paddingLeft() + renderer->paddingRight(); clientWidth += renderer->borderLeft() + renderer->borderRight(); } - return convertToNonSubpixelValue(Style::adjustLayoutUnitForAbsoluteZoom(clientWidth, *renderer).toDouble()); + return convertToNonSubpixelValue(Style::unapplyingZoom(clientWidth, *renderer).toDouble()); } return 0; } @@ -1698,7 +1698,7 @@ int Element::clientHeight() // When in quirks mode, clientHeight for the body element should return the height of the containing frame. bool inQuirksMode = document->inQuirksMode(); if ((!inQuirksMode && document->documentElement() == this) || (inQuirksMode && isHTMLElement() && document->bodyOrFrameset() == this)) - return Style::adjustForAbsoluteZoom(protect(renderView->frameView())->layoutHeight(), renderView); + return Style::unapplyingZoom(protect(renderView->frameView())->layoutHeight(), renderView); if (CheckedPtr renderer = renderBox()) { auto clientHeight = LayoutUnit { roundToInt(renderer->paddingBoxHeight()) }; @@ -1717,7 +1717,7 @@ int Element::clientHeight() clientHeight += renderer->paddingTop() + renderer->paddingBottom(); clientHeight += renderer->borderTop() + renderer->borderBottom(); } - return convertToNonSubpixelValue(Style::adjustLayoutUnitForAbsoluteZoom(clientHeight, *renderer).toDouble()); + return convertToNonSubpixelValue(Style::unapplyingZoom(clientHeight, *renderer).toDouble()); } return 0; } @@ -1757,7 +1757,7 @@ int Element::scrollLeft() } if (CheckedPtr renderer = renderBox()) - return Style::adjustForAbsoluteZoom(renderer->scrollLeft(), *renderer); + return Style::unapplyingZoom(renderer->scrollLeft(), *renderer); return 0; } @@ -1773,7 +1773,7 @@ int Element::scrollTop() } if (CheckedPtr renderer = renderBox()) - return Style::adjustForAbsoluteZoom(renderer->scrollTop(), *renderer); + return Style::unapplyingZoom(renderer->scrollTop(), *renderer); return 0; } @@ -1847,7 +1847,7 @@ int Element::scrollWidth() } if (CheckedPtr renderer = renderBox()) - return Style::adjustForAbsoluteZoom(renderer->scrollWidth(), *renderer); + return Style::unapplyingZoom(renderer->scrollWidth(), *renderer); return 0; } @@ -1865,7 +1865,7 @@ int Element::scrollHeight() } if (CheckedPtr renderer = renderBox()) - return Style::adjustForAbsoluteZoom(renderer->scrollHeight(), *renderer); + return Style::unapplyingZoom(renderer->scrollHeight(), *renderer); return 0; } diff --git a/Source/WebCore/dom/ImageOverlay.cpp b/Source/WebCore/dom/ImageOverlay.cpp index 7f453d6a3055..31712aac1037 100644 --- a/Source/WebCore/dom/ImageOverlay.cpp +++ b/Source/WebCore/dom/ImageOverlay.cpp @@ -565,8 +565,8 @@ void updateWithTextRecognitionResult(HTMLElement& element, const TextRecognition FloatSize sizeBeforeTransform; if (CheckedPtr renderer = textContainer->renderBoxModelObject()) { sizeBeforeTransform = { - Style::adjustLayoutUnitForAbsoluteZoom(renderer->offsetWidth(), *renderer).toFloat(), - Style::adjustLayoutUnitForAbsoluteZoom(renderer->offsetHeight(), *renderer).toFloat(), + Style::unapplyingZoom(renderer->offsetWidth(), *renderer).toFloat(), + Style::unapplyingZoom(renderer->offsetHeight(), *renderer).toFloat(), }; } diff --git a/Source/WebCore/dom/ViewTransition.cpp b/Source/WebCore/dom/ViewTransition.cpp index ff18981fa020..e7343d2ec451 100644 --- a/Source/WebCore/dom/ViewTransition.cpp +++ b/Source/WebCore/dom/ViewTransition.cpp @@ -992,7 +992,7 @@ void ViewTransition::copyElementBaseProperties(RenderLayerModelObject& renderer, // Factor out the zoom from the nearest common ancestor of the captured element and the view transition // pseudo tree (the document element), so that it doesn't get applied a second time when rendering the // snapshots. - LayoutSize cssSize = Style::adjustLayoutSizeForAbsoluteZoom(output.size, documentElementRenderer->style()); + auto cssSize = Style::unapplyingZoom(output.size, documentElementRenderer->style()); protect(output.properties)->setProperty(CSSPropertyWidth, CSSPrimitiveValue::create(cssSize.width(), CSSUnitType::Px)); protect(output.properties)->setProperty(CSSPropertyHeight, CSSPrimitiveValue::create(cssSize.height(), CSSUnitType::Px)); } diff --git a/Source/WebCore/html/HTMLImageElement.cpp b/Source/WebCore/html/HTMLImageElement.cpp index 51f1e3ad6a46..46de95949dd4 100644 --- a/Source/WebCore/html/HTMLImageElement.cpp +++ b/Source/WebCore/html/HTMLImageElement.cpp @@ -677,7 +677,7 @@ unsigned HTMLImageElement::width() if (!box) return 0; LayoutRect contentRect = box->contentBoxRect(); - return Style::adjustLayoutUnitForAbsoluteZoom(contentRect.width(), *box).round(); + return Style::unapplyingZoom(contentRect.width(), *box).round(); } unsigned HTMLImageElement::height() @@ -700,7 +700,7 @@ unsigned HTMLImageElement::height() if (!box) return 0; LayoutRect contentRect = box->contentBoxRect(); - return Style::adjustLayoutUnitForAbsoluteZoom(contentRect.height(), *box).round(); + return Style::unapplyingZoom(contentRect.height(), *box).round(); } unsigned HTMLImageElement::naturalWidth() const diff --git a/Source/WebCore/html/ImageInputType.cpp b/Source/WebCore/html/ImageInputType.cpp index 1c7f32d0a09e..95eea5720ff0 100644 --- a/Source/WebCore/html/ImageInputType.cpp +++ b/Source/WebCore/html/ImageInputType.cpp @@ -179,7 +179,7 @@ unsigned ImageInputType::height() const CheckedPtr renderer = element->renderer(); if (renderer) - return Style::adjustForAbsoluteZoom(downcast(*renderer).contentBoxHeight(), *renderer); + return Style::unapplyingZoom(downcast(*renderer).contentBoxHeight(), *renderer); // Check the attribute first for an explicit pixel value. if (auto optionalHeight = parseHTMLNonNegativeInteger(element->attributeWithoutSynchronization(heightAttr))) @@ -202,7 +202,7 @@ unsigned ImageInputType::width() const CheckedPtr renderer = element->renderer(); if (renderer) - return Style::adjustForAbsoluteZoom(downcast(*renderer).contentBoxWidth(), *renderer); + return Style::unapplyingZoom(downcast(*renderer).contentBoxWidth(), *renderer); // Check the attribute first for an explicit pixel value. if (auto optionalWidth = parseHTMLNonNegativeInteger(element->attributeWithoutSynchronization(widthAttr))) diff --git a/Source/WebCore/inspector/InspectorOverlay.cpp b/Source/WebCore/inspector/InspectorOverlay.cpp index 94524c7e2cbd..edbe10a2afed 100644 --- a/Source/WebCore/inspector/InspectorOverlay.cpp +++ b/Source/WebCore/inspector/InspectorOverlay.cpp @@ -1195,8 +1195,8 @@ Path InspectorOverlay::drawElementTitle(GraphicsContext& context, Node& node, co String elementHeight; if (is(renderer)) { CheckedPtr modelObject = downcast(renderer.get()); - elementWidth = String::number(Style::adjustForAbsoluteZoom(roundToInt(modelObject->offsetWidth()), *modelObject)); - elementHeight = String::number(Style::adjustForAbsoluteZoom(roundToInt(modelObject->offsetHeight()), *modelObject)); + elementWidth = String::number(Style::unapplyingZoom(roundToInt(modelObject->offsetWidth()), *modelObject)); + elementHeight = String::number(Style::unapplyingZoom(roundToInt(modelObject->offsetHeight()), *modelObject)); } else { RefPtr containingView = node.document().frame()->view(); IntRect boundingBox = snappedIntRect(containingView->contentsToRootView(renderer->absoluteBoundingBoxRect())); diff --git a/Source/WebCore/page/ResizeObservation.cpp b/Source/WebCore/page/ResizeObservation.cpp index 9bc8642b834b..952d0e25fa83 100644 --- a/Source/WebCore/page/ResizeObservation.cpp +++ b/Source/WebCore/page/ResizeObservation.cpp @@ -81,9 +81,9 @@ auto ResizeObservation::computeObservedSizes() const -> std::optional if (box->isSkippedContent()) return std::nullopt; return { { - Style::adjustLayoutSizeForAbsoluteZoom(box->contentBoxSize(), *box), - Style::adjustLayoutSizeForAbsoluteZoom(box->contentBoxLogicalSize(), *box), - Style::adjustLayoutSizeForAbsoluteZoom(box->logicalSize(), *box) + Style::unapplyingZoom(box->contentBoxSize(), *box), + Style::unapplyingZoom(box->contentBoxLogicalSize(), *box), + Style::unapplyingZoom(box->logicalSize(), *box) } }; } } diff --git a/Source/WebCore/style/StyleExtractor.cpp b/Source/WebCore/style/StyleExtractor.cpp index 00ee384c44ee..2b945dc3c449 100644 --- a/Source/WebCore/style/StyleExtractor.cpp +++ b/Source/WebCore/style/StyleExtractor.cpp @@ -114,7 +114,7 @@ RefPtr Extractor::getFontSizeCSSValuePreferringKeyword() const if (auto sizeIdentifier = style->fontDescription().keywordSizeAsIdentifier()) return CSSKeywordValue::create(sizeIdentifier); - return CSSPrimitiveValue::create(adjustFloatForAbsoluteZoom(style->fontDescription().computedSize(), *style), CSSUnitType::Px); + return CSSPrimitiveValue::create(unapplyingZoom(style->fontDescription().computedSize(), *style), CSSUnitType::Px); } bool Extractor::useFixedFontDefaultSize() const diff --git a/Source/WebCore/style/values/viewport/StyleZoomPrimitives.h b/Source/WebCore/style/values/viewport/StyleZoomPrimitives.h index 8c880819d344..45d3ebf46715 100644 --- a/Source/WebCore/style/values/viewport/StyleZoomPrimitives.h +++ b/Source/WebCore/style/values/viewport/StyleZoomPrimitives.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 Samuel Weinig + * Copyright (C) 2025-2026 Samuel Weinig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -26,8 +26,6 @@ namespace WebCore { -class LayoutSize; -class LayoutUnit; class RenderElement; namespace Style { @@ -45,18 +43,19 @@ struct ZoomFactor { constexpr bool operator==(const ZoomFactor&) const = default; }; -// Map from values with zoom applied to web-exposed values, which are zoom-independent. -inline int adjustForAbsoluteZoom(int, const ComputedStyle&); -inline int adjustForAbsoluteZoom(int, const RenderElement&); -inline float adjustFloatForAbsoluteZoom(float, const ComputedStyle&); -inline float adjustFloatForAbsoluteZoom(float, const RenderElement&); -inline LayoutUnit adjustLayoutUnitForAbsoluteZoom(LayoutUnit, const ComputedStyle&); -inline LayoutUnit adjustLayoutUnitForAbsoluteZoom(LayoutUnit, const RenderElement&); -inline LayoutSize adjustLayoutSizeForAbsoluteZoom(LayoutSize, const ComputedStyle&); -inline LayoutSize adjustLayoutSizeForAbsoluteZoom(LayoutSize, const RenderElement&); - -// Map from zoom-independent style values to with zoom applied. -inline float applyZoom(float, const ComputedStyle&); +// Map from values with zoom applied to values which are zoom-independent. +template +T unapplyingZoom(T, const ComputedStyle&); + +template +T unapplyingZoom(T, const RenderElement&); + +// Map from values which are zoom-independent to values with zoom applied. +template +T applyingZoom(T, const ComputedStyle&); + +template +T applyingZoom(T, const RenderElement&); } // namespace Style } // namespace WebCore diff --git a/Source/WebCore/style/values/viewport/StyleZoomPrimitivesInlines.h b/Source/WebCore/style/values/viewport/StyleZoomPrimitivesInlines.h index cec81821c726..2e72be380e90 100644 --- a/Source/WebCore/style/values/viewport/StyleZoomPrimitivesInlines.h +++ b/Source/WebCore/style/values/viewport/StyleZoomPrimitivesInlines.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 Samuel Weinig + * Copyright (C) 2025-2026 Samuel Weinig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -29,65 +29,50 @@ #include "StyleComputedStyle+GettersInlines.h" #include "StylePrimitiveNumericTypes+Rounding.h" #include "StyleZoomPrimitives.h" +#include namespace WebCore { namespace Style { -inline int adjustForAbsoluteZoom(int value, const ComputedStyle& style) +template +T unapplyingZoom(T value, const ComputedStyle& style) { - double zoomFactor = style.usedZoom(); - if (zoomFactor == 1) - return value; - // Needed because resolveAsLength truncates (rather than rounds) when scaling up. - if (zoomFactor > 1) { - if (value < 0) - value--; - else - value++; - } - - return roundForImpreciseConversion(value / zoomFactor); -} - -inline int adjustForAbsoluteZoom(int value, const RenderElement& renderer) -{ - return adjustForAbsoluteZoom(value, renderer.style()); -} - -inline float adjustFloatForAbsoluteZoom(float value, const ComputedStyle& style) -{ - return value / style.usedZoom(); -} - -inline float adjustFloatForAbsoluteZoom(float value, const RenderElement& renderer) -{ - return adjustFloatForAbsoluteZoom(value, renderer.style()); -} - -inline LayoutUnit adjustLayoutUnitForAbsoluteZoom(LayoutUnit value, const ComputedStyle& style) -{ - return LayoutUnit(value / style.usedZoom()); -} + auto zoom = style.usedZoom(); -inline LayoutUnit adjustLayoutUnitForAbsoluteZoom(LayoutUnit value, const RenderElement& renderer) -{ - return adjustLayoutUnitForAbsoluteZoom(value, renderer.style()); + if constexpr (std::integral) { + if (zoom == 1) + return value; + // Needed to match historical `CSSPrimitiveValue::resolveAsLength` behavior which truncated (rather than rounding) when scaling up. + if (zoom > 1) { + if (value < 0) + value--; + else + value++; + } + return roundForImpreciseConversion(value / zoom); + } else if constexpr (std::floating_point || std::same_as) { + return T(value / zoom); + } else if constexpr (std::same_as) { + return T(value.width() / zoom, value.height() / zoom); + } } -inline LayoutSize adjustLayoutSizeForAbsoluteZoom(LayoutSize size, const ComputedStyle& style) +template +T unapplyingZoom(T value, const RenderElement& renderer) { - auto zoom = style.usedZoom(); - return { size.width() / zoom, size.height() / zoom }; + return Style::unapplyingZoom(value, renderer.style()); } -inline LayoutSize adjustLayoutSizeForAbsoluteZoom(LayoutSize size, const RenderElement& renderer) +template +inline T applyingZoom(T value, const ComputedStyle& style) { - return adjustLayoutSizeForAbsoluteZoom(size, renderer.style()); + return value * style.usedZoom(); } -inline float applyZoom(float value, const ComputedStyle& style) +template +inline T applyingZoom(T value, const RenderElement& renderer) { - return value * style.usedZoom(); + return Style::applyingZoom(value, renderer.style()); } } // namespace Style From 937b1dde4169fcf3e7d28b7f22884c6f30092640 Mon Sep 17 00:00:00 2001 From: Cole Carley Date: Fri, 28 Aug 2026 14:34:26 -0700 Subject: [PATCH 071/103] [Quirks] Make QuirkMatch a generic URL matcher https://bugs.webkit.org/show_bug.cgi?id=322857 rdar://186099985 Reviewed by Brent Fulgham. QuirkMatch is almost a generic URL matcher, but it has ties to the way we want to match for site specific Quirks. This refactor severs those ties, which results in a flexible new URLMatch class. The refactor also simplified the implementation of the URLMatch class. The site specific Quirk matching logic is now held in a small wrapper class called QuirkURLMatch. This patch was motivated by the inability to use the old QuirkMatch class with just a singular URL, which is needed for the static Quirks declared in Quirks.h. Tests: Tools/TestWebKitAPI/Tests/WebCore/Quirks.cpp Tools/TestWebKitAPI/Tests/WebCore/URLMatch.cpp * Source/WebCore/Headers.cmake: * Source/WebCore/Sources.txt: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/page/QuirkTable.cpp: (WebCore::QuirkURLMatch::matches const): (WebCore::resolveSiteSpecificQuirks): * Source/WebCore/page/QuirkTable.h: (WebCore::QuirkURLMatch::QuirkURLMatch): (WebCore::QuirkURLMatch::embeddedDocument): (WebCore::QuirkURLMatch::embeddedDocumentInTopMatch): * Source/WebCore/page/Quirks.cpp: * Source/WebCore/page/URLMatch.cpp: Renamed from Source/WebCore/page/QuirkMatch.cpp. (WebCore::URLMatchContext::registrableDomain const): (WebCore::URLMatchContext::domainWithoutPublicSuffix const): (WebCore::evaluateURLEnvironment): (WebCore::URLMatch::RefinementSet::matchesPathPattern const): (WebCore::URLMatch::RefinementSet::matches const): (WebCore::URLMatch::matchesURL const): (WebCore::URLMatch::matches const): * Source/WebCore/page/URLMatch.h: Renamed from Source/WebCore/page/QuirkMatch.h. (WebCore::URLMatchContext::URLMatchContext): (WebCore::URLPatternList::URLPatternList): (WebCore::URLPatternList::isEmpty const): (WebCore::URLPatternList::contains const): (WebCore::URLPatternList::containsMatching const): (WebCore::URLRefinement::pathContains): (WebCore::URLRefinement::pathStartsWith): (WebCore::URLRefinement::pathOrFragmentContains): (WebCore::URLRefinement::hostIs): (WebCore::URLRefinement::smallScreen): (WebCore::URLRefinement::tubularApp): (WebCore::URLRefinement::lensApp): (WebCore::URLMatch::domain): (WebCore::URLMatch::host): (WebCore::URLMatch::hostOrSubdomainOf): (WebCore::URLMatch::anyTopLevelDomain): (WebCore::URLMatch::anyURL): (WebCore::URLMatch::when): (WebCore::URLMatch::exceptWhen): (WebCore::URLMatch::setPathPattern): (WebCore::URLMatch::applyRefinement): (WebCore::URLMatch::URLMatch): * Tools/TestWebKitAPI/CMakeLists.txt: * Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: * Tools/TestWebKitAPI/Tests/WebCore/QuirkMatch.cpp: Removed. * Tools/TestWebKitAPI/Tests/WebCore/Quirks.cpp: (TestWebKitAPI::resolveQuirksForTopURL): (TestWebKitAPI::resolveQuirksForEmbeddedDocument): (TestWebKitAPI::matchesTopURL): (TestWebKitAPI::matchesEmbeddedDocument): (TestWebKitAPI::TEST_F(QuirksTest, TopURLMatchIgnoresTheDocumentURL)): (TestWebKitAPI::TEST_F(QuirksTest, EmbeddedDocumentMatchesTheDocumentURLNotTheTopURL)): (TestWebKitAPI::TEST_F(QuirksTest, EmbeddedDocumentInTopMatchRequiresBothURLsToMatch)): (TestWebKitAPI::TEST_F(QuirksTest, EmbeddedMatchesNeverApplyToTheTopDocument)): (TestWebKitAPI::TEST_F(QuirksTest, EmbeddedQuirksResolveFromTheDocumentURL)): * Tools/TestWebKitAPI/Tests/WebCore/URLMatch.cpp: Added. (TestWebKitAPI::matchesURL): (TestWebKitAPI::TEST(URLMatchTest, DomainMatchesRegistrableDomain)): (TestWebKitAPI::TEST(URLMatchTest, DomainUnderstandsMultiLabelPublicSuffixes)): (TestWebKitAPI::TEST(URLMatchTest, DomainsMatchesAnyPatternInTheList)): (TestWebKitAPI::TEST(URLMatchTest, HostMatchesExactHostOnly)): (TestWebKitAPI::TEST(URLMatchTest, HostOrSubdomainOfRespectsLabelBoundaries)): (TestWebKitAPI::TEST(URLMatchTest, HostOrSubdomainOfCoversShardedHosts)): (TestWebKitAPI::TEST(URLMatchTest, AnyTopLevelDomainMatchesEveryPublicSuffix)): (TestWebKitAPI::TEST(URLMatchTest, PathContainsMatchesAnywhereInThePath)): (TestWebKitAPI::TEST(URLMatchTest, PathStartsWithIsAnchored)): (TestWebKitAPI::TEST(URLMatchTest, PathOrFragmentContainsSearchesBoth)): (TestWebKitAPI::TEST(URLMatchTest, EnvironmentIsANDedWithTheSiteMatch)): (TestWebKitAPI::TEST(URLMatchTest, AnyURLMatchesEverySiteWithoutFurtherRefinement)): (TestWebKitAPI::TEST(URLMatchTest, ExceptWhenCarvesOutPagesOfAMatchedSite)): (TestWebKitAPI::TEST(URLMatchTest, ExceptWhenCarvesOutHostsOfAMatchedSite)): (TestWebKitAPI::TEST(URLMatchTest, ExceptWhenCarvesOutASingleHost)): (TestWebKitAPI::TEST(URLMatchTest, HostIsNarrowsAMatchToOneHost)): (TestWebKitAPI::TEST(URLMatchTest, ExceptWhenAndTheMatchKeepSeparateRefinements)): (TestWebKitAPI::TEST(URLMatchTest, EnvironmentStacksWithAPathRefinement)): (TestWebKitAPI::TEST(URLMatchTest, HostsWithoutAPublicSuffixFallBackToTheHost)): (TestWebKitAPI::TEST(URLMatchTest, URLsWithoutAHostMatchNothing)): (TestWebKitAPI::TEST(URLMatchTest, ContextDerivesValuesFromItsURL)): (TestWebKitAPI::TEST(URLMatchTest, ContextCachesDerivedValues)): Canonical link: https://commits.webkit.org/320079@main --- Source/WebCore/Headers.cmake | 2 +- Source/WebCore/Sources.txt | 2 +- .../WebCore/WebCore.xcodeproj/project.pbxproj | 12 +- Source/WebCore/page/QuirkTable.cpp | 289 +++++++------ Source/WebCore/page/QuirkTable.h | 43 +- Source/WebCore/page/Quirks.cpp | 33 +- .../page/{QuirkMatch.cpp => URLMatch.cpp} | 95 ++-- .../WebCore/page/{QuirkMatch.h => URLMatch.h} | 196 ++++----- Tools/TestWebKitAPI/CMakeLists.txt | 2 +- .../TestWebKitAPI.xcodeproj/project.pbxproj | 2 +- .../Tests/WebCore/QuirkMatch.cpp | 404 ------------------ Tools/TestWebKitAPI/Tests/WebCore/Quirks.cpp | 79 +++- .../TestWebKitAPI/Tests/WebCore/URLMatch.cpp | 310 ++++++++++++++ 13 files changed, 731 insertions(+), 738 deletions(-) rename Source/WebCore/page/{QuirkMatch.cpp => URLMatch.cpp} (50%) rename Source/WebCore/page/{QuirkMatch.h => URLMatch.h} (56%) delete mode 100644 Tools/TestWebKitAPI/Tests/WebCore/QuirkMatch.cpp create mode 100644 Tools/TestWebKitAPI/Tests/WebCore/URLMatch.cpp diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake index fa3788d8d19d..98b48cc1450d 100644 --- a/Source/WebCore/Headers.cmake +++ b/Source/WebCore/Headers.cmake @@ -2082,7 +2082,6 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS page/PrewarmInformation.h page/PrintContext.h page/ProcessWarming.h - page/QuirkMatch.h page/QuirkNames.h page/QuirkTable.h page/Quirks.h @@ -2118,6 +2117,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS page/TextAnimationTypes.h page/TextDirectionSubmenuInclusionBehavior.h page/TextIndicator.h + page/URLMatch.h page/TranslationContextMenuInfo.h page/UADataValues.h page/UALowEntropyJSON.h diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt index f67d63443210..696a7bde7b54 100644 --- a/Source/WebCore/Sources.txt +++ b/Source/WebCore/Sources.txt @@ -2357,7 +2357,6 @@ page/PointerCaptureController.cpp page/PointerLockController.cpp page/PrintContext.cpp @header:RenderStyleGetters @cost:7 page/ProcessWarming.cpp -page/QuirkMatch.cpp page/QuirkTable.cpp page/Quirks.cpp @header:RenderStyleGetters @cost:11 page/RemoteDOMWindow.cpp @@ -2382,6 +2381,7 @@ page/ShadowRealmGlobalScope.cpp page/ShareDataReader.cpp page/SpatialNavigation.cpp @header:RenderStyleGetters @cost:6 page/TextIndicator.cpp @header:RenderStyleGetters @cost:6 +page/URLMatch.cpp page/UndoItem.cpp page/UndoManager.cpp page/UserContentController.cpp diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj index 858843875d7d..f4dc68aff4dd 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj @@ -3164,7 +3164,7 @@ 05D4770CFFB4C18C6BA6928C /* QuirkTable.h in Headers */ = {isa = PBXBuildFile; fileRef = 05D4770BFFB4C18C6BA6928C /* QuirkTable.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7ABB0F622CE7C170009A837F /* QuirkNames.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ABB0F5D2CE7C170009A837F /* QuirkNames.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7ABB0F602CE7C170009A837D /* QuirksData.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ABB0F5F2CE7C170009A837D /* QuirksData.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 05D47708FFB4C18C6BA6928A /* QuirkMatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 05D47707FFB4C18C6BA6928A /* QuirkMatch.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 05D47708FFB4C18C6BA6928A /* URLMatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 05D47707FFB4C18C6BA6928A /* URLMatch.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7AC09F4728C0122C004568FC /* TestReportBody.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AC09F4528C00E5D004568FC /* TestReportBody.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7AD41E7E2C7607AF00ED9467 /* ScrollingPlatformLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AD41E7D2C7607AA00ED9467 /* ScrollingPlatformLayer.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7ADE722610CBBB9B006B3B3A /* ContextMenuProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ADE722510CBBB9B006B3B3A /* ContextMenuProvider.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -14997,10 +14997,10 @@ 7ABA250E28B40370005D4F6D /* ReportingClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReportingClient.h; sourceTree = ""; }; 7ABB0F5D2CE7C170009A837F /* QuirkNames.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuirkNames.h; sourceTree = ""; }; 7ABB0F5F2CE7C170009A837D /* QuirksData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuirksData.h; sourceTree = ""; }; - 05D47709FFB4C18C6BA6928A /* QuirkMatch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = QuirkMatch.cpp; sourceTree = ""; }; + 05D47709FFB4C18C6BA6928A /* URLMatch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = URLMatch.cpp; sourceTree = ""; }; 05D4770AFFB4C18C6BA6928B /* QuirkTable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = QuirkTable.cpp; sourceTree = ""; }; 05D4770BFFB4C18C6BA6928C /* QuirkTable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuirkTable.h; sourceTree = ""; }; - 05D47707FFB4C18C6BA6928A /* QuirkMatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuirkMatch.h; sourceTree = ""; }; + 05D47707FFB4C18C6BA6928A /* URLMatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = URLMatch.h; sourceTree = ""; }; 7ABF02D621D7EBA2AFFD021A /* UnifiedSource35-header-RenderStyleGetters.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "UnifiedSource35-header-RenderStyleGetters.cpp"; sourceTree = ""; }; 7AC09F3628BFD45B004568FC /* JSReportBody.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSReportBody.h; sourceTree = ""; }; 7AC09F3728BFD45B004568FC /* JSReportBody.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSReportBody.cpp; sourceTree = ""; }; @@ -31537,8 +31537,6 @@ B776D43A1104525D00BEB0EC /* PrintContext.h */, E42050162141901B0066EF3B /* ProcessWarming.cpp */, E42050142141901A0066EF3B /* ProcessWarming.h */, - 05D47709FFB4C18C6BA6928A /* QuirkMatch.cpp */, - 05D47707FFB4C18C6BA6928A /* QuirkMatch.h */, 7ABB0F5D2CE7C170009A837F /* QuirkNames.h */, CD9A87F9215D6CF3006F17B5 /* Quirks.cpp */, CD9A87FB215D6CF3006F17B5 /* Quirks.h */, @@ -31642,6 +31640,8 @@ 2ECDBACF21D8903400F00ECD /* UndoManager.cpp */, 2ECDBACE21D8903400F00ECD /* UndoManager.h */, 2ECDBAD021D8903400F00ECD /* UndoManager.idl */, + 05D47709FFB4C18C6BA6928A /* URLMatch.cpp */, + 05D47707FFB4C18C6BA6928A /* URLMatch.h */, D6F1F4B32E42AAA200FAB161 /* UserAgentStringData.h */, D61496E22E428EEE00BAF335 /* UserAgentStringParser.cpp */, D61496E12E428EDB00BAF335 /* UserAgentStringParser.h */, @@ -48296,7 +48296,6 @@ A70B77D62A9EE40B003D8566 /* Quaternion.h in Headers */, CD20ED3C27878FFB0038BE44 /* QueuedVideoOutput.h in Headers */, A15E31F41E0CB0B5004B371C /* QuickLook.h in Headers */, - 05D47708FFB4C18C6BA6928A /* QuirkMatch.h in Headers */, 7ABB0F622CE7C170009A837F /* QuirkNames.h in Headers */, 9BAEE92C22388A7D004157A9 /* Quirks.h in Headers */, 7ABB0F602CE7C170009A837D /* QuirksData.h in Headers */, @@ -49924,6 +49923,7 @@ 267726051A5DF6F2003C24DD /* URLFilterParser.h in Headers */, F55B3DDE1251F12D003EF269 /* URLInputType.h in Headers */, 4674BE5128C9A5B800A6C831 /* URLKeepingBlobAlive.h in Headers */, + 05D47708FFB4C18C6BA6928A /* URLMatch.h in Headers */, 900C20842CAF8232002C79C5 /* URLPattern.h in Headers */, 90E1B6D22CB9A7E300CADF80 /* URLPatternCanonical.h in Headers */, 90E0A16F2CDEE2CF0074377B /* URLPatternComponent.h in Headers */, diff --git a/Source/WebCore/page/QuirkTable.cpp b/Source/WebCore/page/QuirkTable.cpp index ac8e53b9586a..8f0c9e838334 100644 --- a/Source/WebCore/page/QuirkTable.cpp +++ b/Source/WebCore/page/QuirkTable.cpp @@ -48,33 +48,33 @@ static constexpr std::array claudeDomains { "claude.ai"_s, "claude.com"_s }; namespace SiteSpecificQuirks { using enum SiteSpecificQuirk; -using namespace QuirkRefinement; +using namespace URLRefinement; static constexpr Quirk table[] = { #if PLATFORM(IOS) || PLATFORM(VISION) // 365scores.com rdar://116491386 - { .match = QuirkMatch::domain("365scores.com"_s), + { .match = URLMatch::domain("365scores.com"_s), .behaviors = { ShouldSilenceWindowResizeEventsDuringApplicationSnapshotting } }, #endif #if ENABLE(MEDIA_STREAM) // actesting.org rdar://124017544 - { .match = QuirkMatch::domain("actesting.org"_s), + { .match = URLMatch::domain("actesting.org"_s), .behaviors = { ShouldEnableLegacyGetUserMediaQuirk } }, #endif // airindiaexpress.com https://webkit.org/b/317375 - { .match = QuirkMatch::domain("airindiaexpress.com"_s), + { .match = URLMatch::domain("airindiaexpress.com"_s), .behaviors = { NeedsAirIndiaExpressLayeringQuirk } }, // Note: There is a userAgent override for rdar://117771731, see needsCustomUserAgentOverride() #if ENABLE(TOUCH_EVENTS) || ENABLE(TOUCH_EVENT_REGIONS) // airtable.com rdar://49124313 - { .match = QuirkMatch::domain("airtable.com"_s), + { .match = URLMatch::domain("airtable.com"_s), .behaviors = { ShouldDispatchSimulatedMouseEventsQuirk } }, #endif - { .match = QuirkMatch::anyTopLevelDomain("amazon"_s), + { .match = URLMatch::anyTopLevelDomain("amazon"_s), .behaviors = { // amazon.com rdar://49124529 ShouldDispatchSimulatedMouseEventsAssumeDefaultPreventedQuirk, @@ -90,31 +90,31 @@ static constexpr Quirk table[] = { .site = QuirkSite::Amazon }, #if PLATFORM(IOS_FAMILY) - { .match = QuirkMatch::domain("amazon.design"_s), + { .match = URLMatch::domain("amazon.design"_s), .behaviors = { NeedsAmazonDesignMenuViewportUnitQuirk } }, #endif // apple.com rdar://154434137 // FIXME: Maybe EnsureCaptionVisibilityInFullscreenAndPictureInPicture should apply to apple.com.cn too? - { .match = QuirkMatch::domain("apple.com"_s), + { .match = URLMatch::domain("apple.com"_s), .behaviors = { EnsureCaptionVisibilityInFullscreenAndPictureInPicture } }, // Quirk added for rdar://181007316, remove when rdar://182134549 is fixed. - { .match = QuirkMatch::anyTopLevelDomain("apple"_s).when(pathContains("/retail"_s)), + { .match = URLMatch::anyTopLevelDomain("apple"_s).when(pathContains("/retail"_s)), .behaviors = { ShouldDisableScrollAnchoringQuirk } }, #if PLATFORM(IOS_FAMILY) // as.com: rdar://121014613 - { .match = QuirkMatch::domain("as.com"_s).when(smallScreen()), + { .match = URLMatch::domain("as.com"_s).when(smallScreen()), .behaviors = { ShouldDisableElementFullscreenQuirk } }, // att.com rdar://55185021 - { .match = QuirkMatch::domain("att.com"_s), + { .match = URLMatch::domain("att.com"_s), .behaviors = { ShouldUseLegacySelectPopoverDismissalBehaviorInDataActivationQuirk } }, #endif // Login issue on bankofamerica.com (rdar://104938789). - { .match = QuirkMatch::domain("bankofamerica.com"_s), + { .match = URLMatch::domain("bankofamerica.com"_s), .behaviors = { MaybeBypassBackForwardCache, }, @@ -122,17 +122,17 @@ static constexpr Quirk table[] = { // bbc.co.uk rdar://126494734 // bbc.com rdar://157499149 - { .match = QuirkMatch::domain(bbcDomains), + { .match = URLMatch::domain(bbcDomains), .behaviors = { ReturnNullPictureInPictureElementDuringFullscreenChangeQuirk } }, // bestbuy.com rdar://136235936 - { .match = QuirkMatch::domain("bestbuy.com"_s), + { .match = URLMatch::domain("bestbuy.com"_s), .behaviors = { NeedsScriptToEvaluateBeforeRunningScriptFromURLQuirk, }, .site = QuirkSite::BestBuy }, - { .match = QuirkMatch::domain("bing.com"_s), + { .match = URLMatch::domain("bing.com"_s), .behaviors = { // bing.com rdar://133223599 MaybeBypassBackForwardCache, @@ -142,22 +142,22 @@ static constexpr Quirk table[] = { .site = QuirkSite::Bing }, // bungalow.com rdar://61658940 - { .match = QuirkMatch::domain("bungalow.com"_s), + { .match = URLMatch::domain("bungalow.com"_s), .behaviors = { ShouldBypassAsyncScriptDeferring } }, - { .match = QuirkMatch::domain("capitalgroup.com"_s), + { .match = URLMatch::domain("capitalgroup.com"_s), .behaviors = { ShouldDelayReloadWhenRegisteringServiceWorker } }, #if PLATFORM(IOS_FAMILY) // Remove this once rdar://139478801 is resolved. - { .match = QuirkMatch::domain("cbssports.com"_s), + { .match = URLMatch::domain("cbssports.com"_s), .behaviors = { ShouldSynthesizeTouchEventsAfterNonSyntheticClickQuirk, }, .site = QuirkSite::CBSSports }, #endif - { .match = QuirkMatch::hostOrSubdomainOf("ceac.state.gov"_s), + { .match = URLMatch::hostOrSubdomainOf("ceac.state.gov"_s), .behaviors = { #if PLATFORM(MAC) // ceac.state.gov https://bugs.webkit.org/show_bug.cgi?id=193478 @@ -168,10 +168,10 @@ static constexpr Quirk table[] = { }, .site = QuirkSite::CEAC }, #if PLATFORM(IOS) - { .match = QuirkMatch::domain("chess.com"_s).when(smallScreen()), + { .match = URLMatch::domain("chess.com"_s).when(smallScreen()), .behaviors = { ShouldEnterNativeFullscreenWhenCallingElementRequestFullscreen } }, #endif - { .match = QuirkMatch::domain("claude.ai"_s), + { .match = URLMatch::domain("claude.ai"_s), .behaviors = { #if PLATFORM(IOS_FAMILY) NeedsClaudeSidebarViewportUnitQuirk, @@ -183,12 +183,12 @@ static constexpr Quirk table[] = { } }, #if PLATFORM(IOS_FAMILY) - { .match = QuirkMatch::domain(claudeDomains), + { .match = URLMatch::domain(claudeDomains), .behaviors = { NeedsHideSelectionDuringOverflowScrollQuirk } }, #endif #if PLATFORM(IOS_FAMILY) - { .match = QuirkMatch::domain("cnn.com"_s), + { .match = URLMatch::domain("cnn.com"_s), .behaviors = { // cnn.com rdar://119640248 NeedsFullscreenObjectFitQuirk, @@ -203,24 +203,24 @@ static constexpr Quirk table[] = { #endif #if ENABLE(MEDIA_STREAM) - { .match = QuirkMatch::host("codepen.io"_s), + { .match = URLMatch::host("codepen.io"_s), .behaviors = { ShouldEnableSpeakerSelectionPermissionsPolicyQuirk } }, #endif - { .match = QuirkMatch::domain("crunchyroll.com"_s), + { .match = URLMatch::domain("crunchyroll.com"_s), .behaviors = { NeedsSuppressPostLayoutBoundaryEventsQuirk } }, - { .match = QuirkMatch::domain("dailymail.co.uk"_s), + { .match = URLMatch::domain("dailymail.co.uk"_s), .behaviors = { ShouldUnloadHeavyFrames } }, - { .match = QuirkMatch::host("digits.t-mobile.com"_s), + { .match = URLMatch::host("digits.t-mobile.com"_s), .behaviors = { NeedsNavigatorUserAgentDataQuirk, NeedsCustomUserAgentData } }, // descript.com rdar://156024693 - { .match = QuirkMatch::domain("descript.com"_s), + { .match = URLMatch::domain("descript.com"_s), .behaviors = { ShouldDisableDOMAudioSession } }, - { .match = QuirkMatch::domain("dictionary.com"_s), + { .match = URLMatch::domain("dictionary.com"_s), .behaviors = { #if PLATFORM(IOS_FAMILY) NeedsScriptToEvaluateBeforeRunningScriptFromURLQuirk, @@ -233,14 +233,14 @@ static constexpr Quirk table[] = { #if PLATFORM(IOS_FAMILY) // digitaltrends.com rdar://121014613 - { .match = QuirkMatch::domain("digitaltrends.com"_s).when(smallScreen()), + { .match = URLMatch::domain("digitaltrends.com"_s).when(smallScreen()), .behaviors = { ShouldDisableElementFullscreenQuirk } }, // discord.com rdar://162719481 - { .match = QuirkMatch::domain("discord.com"_s), + { .match = URLMatch::domain("discord.com"_s), .behaviors = { ShouldUseLayoutViewportForClientRectsQuirk } }, - { .match = QuirkMatch::domain("disneyplus.com"_s), + { .match = URLMatch::domain("disneyplus.com"_s), .behaviors = { // disneyplus rdar://137613110 ShouldHideCoarsePointerCharacteristicsQuirk, @@ -251,10 +251,10 @@ static constexpr Quirk table[] = { } }, #endif - { .match = QuirkMatch::domain("ea.com"_s), + { .match = URLMatch::domain("ea.com"_s), .site = QuirkSite::EA }, - { .match = QuirkMatch::domain("espn.com"_s), + { .match = URLMatch::domain("espn.com"_s), .behaviors = { #if PLATFORM(IOS) // espn.com rdar://184169028 @@ -271,14 +271,14 @@ static constexpr Quirk table[] = { } }, // Expedia Group rdar://126631968 - { .match = QuirkMatch::domain(expediaGroupDomains), + { .match = URLMatch::domain(expediaGroupDomains), .behaviors = { NeedsExpediaGroupAnimationQuirk } }, - { .match = QuirkMatch::anyTopLevelDomain("ebookers"_s), + { .match = URLMatch::anyTopLevelDomain("ebookers"_s), .behaviors = { NeedsExpediaGroupAnimationQuirk } }, - { .match = QuirkMatch::anyTopLevelDomain("expedia"_s), + { .match = URLMatch::anyTopLevelDomain("expedia"_s), .behaviors = { NeedsExpediaGroupAnimationQuirk } }, - { .match = QuirkMatch::domain("facebook.com"_s), + { .match = URLMatch::domain("facebook.com"_s), .behaviors = { // facebook.com rdar://100871402 NeedsFacebookRemoveNotSupportedQuirk, @@ -310,33 +310,33 @@ static constexpr Quirk table[] = { #if ENABLE(TOUCH_EVENTS) || ENABLE(TOUCH_EVENT_REGIONS) // flipkart.com rdar://49648520 - { .match = QuirkMatch::domain("flipkart.com"_s), + { .match = URLMatch::domain("flipkart.com"_s), .behaviors = { ShouldDispatchSimulatedMouseEventsQuirk } }, #endif #if ENABLE(VIDEO_PRESENTATION_MODE) // forbes.com rdar://67273166 - { .match = QuirkMatch::domain("forbes.com"_s), + { .match = URLMatch::domain("forbes.com"_s), .behaviors = { RequiresUserGestureToPauseInPictureInPictureQuirk } }, #endif - { .match = QuirkMatch::host("play.geforcenow.com"_s), + { .match = URLMatch::host("play.geforcenow.com"_s), .behaviors = { NeedsGeforcenowWarningDisplayNoneQuirk } }, #if PLATFORM(IOS_FAMILY) // gizmodo.com rdar://102227302 - { .match = QuirkMatch::domain("gizmodo.com"_s), + { .match = URLMatch::domain("gizmodo.com"_s), .behaviors = { NeedsFullscreenDisplayNoneQuirk } }, #endif - { .match = QuirkMatch::anyTopLevelDomain("google"_s), + { .match = URLMatch::anyTopLevelDomain("google"_s), .behaviors = { // docs.google.com rdar://59893415 MaybeBypassBackForwardCache, }, .site = QuirkSite::GoogleProperty }, - { .match = QuirkMatch::anyTopLevelDomain("google"_s).when(pathStartsWith("/maps/"_s)), + { .match = URLMatch::anyTopLevelDomain("google"_s).when(pathStartsWith("/maps/"_s)), .behaviors = { #if ENABLE(TWO_PHASE_CLICKS) // maps.google.com rdar://152194074 @@ -355,7 +355,7 @@ static constexpr Quirk table[] = { }, .site = QuirkSite::GoogleMaps }, - { .match = QuirkMatch::host("docs.google.com"_s), + { .match = URLMatch::host("docs.google.com"_s), .behaviors = { InputMethodUsesCorrectKeyEventOrder, #if PLATFORM(MAC) @@ -372,17 +372,17 @@ static constexpr Quirk table[] = { #if PLATFORM(IOS_FAMILY) // docs.google.com https://bugs.webkit.org/show_bug.cgi?id=199587 - { .match = QuirkMatch::host("docs.google.com"_s).when(pathStartsWith("/spreadsheets/"_s)), + { .match = URLMatch::host("docs.google.com"_s).when(pathStartsWith("/spreadsheets/"_s)), .behaviors = { NeedsDeferKeyDownAndKeyPressTimersUntilNextEditingCommandQuirk } }, - { .match = QuirkMatch::host("docs.google.com"_s).when(pathStartsWith("/presentation/"_s)), + { .match = URLMatch::host("docs.google.com"_s).when(pathStartsWith("/presentation/"_s)), .behaviors = { ShouldIgnoreInputModeNone } }, // mail.google.com rdar://49403416 - { .match = QuirkMatch::host("mail.google.com"_s), + { .match = URLMatch::host("mail.google.com"_s), .behaviors = { NeedsGMailOverflowScrollQuirk } }, - { .match = QuirkMatch::host("translate.google.com"_s), + { .match = URLMatch::host("translate.google.com"_s), .behaviors = { // translate.google.com rdar://106539018 NeedsGoogleTranslateScrollingQuirk, @@ -392,20 +392,20 @@ static constexpr Quirk table[] = { #if ENABLE(TOUCH_EVENTS) || ENABLE(TOUCH_EVENT_REGIONS) // sites.google.com rdar://58653069 - { .match = QuirkMatch::host("sites.google.com"_s), + { .match = URLMatch::host("sites.google.com"_s), .behaviors = { ShouldPreventDispatchOfTouchEventQuirk } }, #endif #if ENABLE(MEDIA_STREAM) - { .match = QuirkMatch::host("meet.google.com"_s), + { .match = URLMatch::host("meet.google.com"_s), .behaviors = { ShouldEnableCameraBackgroundPlayback } }, #endif // hbomax.com https://bugs.webkit.org/show_bug.cgi?id=244737 - { .match = QuirkMatch::domain("hbomax.com"_s), + { .match = URLMatch::domain("hbomax.com"_s), .behaviors = { ShouldEnableFontLoadingAPIQuirk } }, - { .match = QuirkMatch::host("play.hbomax.com"_s), + { .match = URLMatch::host("play.hbomax.com"_s), .behaviors = { #if HAVE(PIP_SKIP_PREROLL) // play.hbomax.com rdar://158430821 @@ -419,7 +419,7 @@ static constexpr Quirk table[] = { #endif } }, - { .match = QuirkMatch::domain("hulu.com"_s), + { .match = URLMatch::domain("hulu.com"_s), .behaviors = { // hulu.com rdar://55041979 NeedsCanPlayAfterSeekedQuirk, @@ -431,22 +431,22 @@ static constexpr Quirk table[] = { #if PLATFORM(IOS_FAMILY) // icloud.com rdar://131836301 - { .match = QuirkMatch::domain("icloud.com"_s).when(pathOrFragmentContains("mail"_s)), + { .match = URLMatch::domain("icloud.com"_s).when(pathOrFragmentContains("mail"_s)), .behaviors = { ShouldSilenceWindowResizeEventsDuringApplicationSnapshotting } }, #endif #if PLATFORM(MAC) // icloud.com rdar://26013388 - { .match = QuirkMatch::domain("icloud.com"_s).when(pathOrFragmentContains("notes"_s)), + { .match = URLMatch::domain("icloud.com"_s).when(pathOrFragmentContains("notes"_s)), .behaviors = { IsNeverRichlyEditableForTouchBarQuirk } }, #endif - { .match = QuirkMatch::domain("iheart.com"_s), + { .match = URLMatch::domain("iheart.com"_s), .behaviors = { NeedsScriptToEvaluateBeforeRunningScriptFromURLQuirk, }, .site = QuirkSite::IHeart }, - { .match = QuirkMatch::domain("imdb.com"_s), + { .match = URLMatch::domain("imdb.com"_s), .behaviors = { // imdb.com: rdar://137991466 NeedsChromeMediaControlsPseudoElementQuirk, @@ -454,7 +454,7 @@ static constexpr Quirk table[] = { NeedsZeroMaxTouchPointsQuirk, } }, - { .match = QuirkMatch::domain("instagram.com"_s), + { .match = URLMatch::domain("instagram.com"_s), .behaviors = { // rdar://166400170 NeedsInstagramResizingReelsQuirk, @@ -466,21 +466,21 @@ static constexpr Quirk table[] = { #if PLATFORM(IOS_FAMILY) // instagram.com rdar://121014613 - { .match = QuirkMatch::domain("instagram.com"_s), + { .match = URLMatch::domain("instagram.com"_s), .behaviors = { ShouldDisableElementFullscreenQuirk } }, #endif // invideo.io rdar://171741842 https://webkit.org/b/311602 - { .match = QuirkMatch::domain("invideo.io"_s), + { .match = URLMatch::domain("invideo.io"_s), .behaviors = { NeedsScriptToEvaluateBeforeRunningScriptFromURLQuirk, }, .site = QuirkSite::InVideo }, - { .match = QuirkMatch::domain("linkedin.com"_s), + { .match = URLMatch::domain("linkedin.com"_s), .site = QuirkSite::LinkedIn }, - { .match = QuirkMatch::domain("live.com"_s), + { .match = URLMatch::domain("live.com"_s), .behaviors = { #if PLATFORM(IOS_FAMILY) // live.com: rdar://167489768 @@ -490,7 +490,7 @@ static constexpr Quirk table[] = { ShouldAvoidResizingWhenInputViewBoundsChangeQuirk, } }, - { .match = QuirkMatch::host("outlook.live.com"_s), + { .match = URLMatch::host("outlook.live.com"_s), .behaviors = { // outlook.live.com: rdar://136624720 NeedsMozillaFileTypeForDataTransferQuirk, @@ -506,28 +506,28 @@ static constexpr Quirk table[] = { .site = QuirkSite::Outlook }, // Microsoft office online generates data URLs with incorrect padding on Safari only (rdar://114573089). - { .match = QuirkMatch::hostOrSubdomainOf("officeapps.live.com"_s), + { .match = URLMatch::hostOrSubdomainOf("officeapps.live.com"_s), .behaviors = { ShouldDisableDataURLPaddingValidation } }, - { .match = QuirkMatch::hostOrSubdomainOf("onedrive.live.com"_s), + { .match = URLMatch::hostOrSubdomainOf("onedrive.live.com"_s), .behaviors = { ShouldDisableDataURLPaddingValidation } }, #if PLATFORM(MAC) // onedrive.live.com rdar://26013388 - { .match = QuirkMatch::host("onedrive.live.com"_s), + { .match = URLMatch::host("onedrive.live.com"_s), .behaviors = { IsNeverRichlyEditableForTouchBarQuirk } }, // madisoncity.k12.al.us https://bugs.webkit.org/show_bug.cgi?id=296989 - { .match = QuirkMatch::domain("madisoncity.k12.al.us"_s), + { .match = URLMatch::domain("madisoncity.k12.al.us"_s), .behaviors = { NeedsFormControlToBeMouseFocusableQuirk } }, #endif #if PLATFORM(IOS_FAMILY) // mailchimp.com rdar://47868965 - { .match = QuirkMatch::domain("mailchimp.com"_s), + { .match = URLMatch::domain("mailchimp.com"_s), .behaviors = { ShouldDisablePointerEventsQuirk } }, #endif - { .match = QuirkMatch::domain("marcus.com"_s), + { .match = URLMatch::domain("marcus.com"_s), .behaviors = { // Marcus: . ShouldExposeShowModalDialog, @@ -538,20 +538,20 @@ static constexpr Quirk table[] = { } }, // medium.com rdar://50457837 - { .match = QuirkMatch::domain("medium.com"_s), + { .match = URLMatch::domain("medium.com"_s), .behaviors = { ShouldDispatchSyntheticMouseEventsWhenModifyingSelectionQuirk } }, #if PLATFORM(IOS_FAMILY) // m365.cloud.microsoft rdar://157794706 - { .match = QuirkMatch::hostOrSubdomainOf("m365.cloud.microsoft"_s), + { .match = URLMatch::hostOrSubdomainOf("m365.cloud.microsoft"_s), .behaviors = { ShouldAllowPopupFromMicrosoftOfficeToOneDrive } }, #endif // safe.menlosecurity.com rdar://135114489 - { .match = QuirkMatch::host("safe.menlosecurity.com"_s), + { .match = URLMatch::host("safe.menlosecurity.com"_s), .behaviors = { ShouldDisableWritingSuggestionsByDefaultQuirk } }, - { .match = QuirkMatch::domain("messenger.com"_s), + { .match = URLMatch::domain("messenger.com"_s), .behaviors = { #if ENABLE(MEDIA_STREAM) // facebook.com rdar://158736355 @@ -568,29 +568,29 @@ static constexpr Quirk table[] = { #if PLATFORM(IOS_FAMILY) // rdar://147429596 - { .match = QuirkMatch::domain("nba.com"_s), + { .match = URLMatch::domain("nba.com"_s), .behaviors = { NeedsScriptToEvaluateBeforeRunningScriptFromURLQuirk, }, .site = QuirkSite::NBA }, #if PLATFORM(IOS) - { .match = QuirkMatch::domain("nba.com"_s).when(smallScreen()), + { .match = URLMatch::domain("nba.com"_s).when(smallScreen()), .behaviors = { ShouldEnterNativeFullscreenWhenCallingElementRequestFullscreen } }, #endif #endif #if ENABLE(TOUCH_EVENTS) || ENABLE(TOUCH_EVENT_REGIONS) // mybinder.org rdar://51770057 - { .match = QuirkMatch::domain("mybinder.org"_s), + { .match = URLMatch::domain("mybinder.org"_s), .behaviors = { ShouldDispatchSimulatedMouseEventsQuirk }, .site = QuirkSite::MyBinder }, // naver.com rdar://48068610 - { .match = QuirkMatch::hostOrSubdomainOf("naver.com"_s).exceptWhen(hostIs(naverHostsWithoutSimulatedMouseEvents)), + { .match = URLMatch::hostOrSubdomainOf("naver.com"_s).exceptWhen(hostIs(naverHostsWithoutSimulatedMouseEvents)), .behaviors = { ShouldDispatchSimulatedMouseEventsQuirk } }, #endif - { .match = QuirkMatch::domain("netflix.com"_s), + { .match = URLMatch::domain("netflix.com"_s), .behaviors = { // netflix.com https://bugs.webkit.org/show_bug.cgi?id=173030 NeedsSeekingSupportDisabledQuirk, @@ -608,28 +608,28 @@ static constexpr Quirk table[] = { }, .site = QuirkSite::Netflix }, - { .match = QuirkMatch::domain("nfl.com"_s), + { .match = URLMatch::domain("nfl.com"_s), .behaviors = { ShouldSuppressHLSSubtitles } }, - { .match = QuirkMatch::domain("nhl.com"_s), + { .match = URLMatch::domain("nhl.com"_s), .behaviors = { NeedsWebKitMediaTextTrackDisplayQuirk } }, #if PLATFORM(IOS) || PLATFORM(VISION) // nytimes.com: rdar://problem/5976384 - { .match = QuirkMatch::domain("nytimes.com"_s), + { .match = URLMatch::domain("nytimes.com"_s), .behaviors = { ShouldSilenceWindowResizeEventsDuringApplicationSnapshotting } }, #endif // Pandora: . - { .match = QuirkMatch::domain("pandora.com"_s), + { .match = URLMatch::domain("pandora.com"_s), .behaviors = { ShouldExposeShowModalDialog } }, // pinterest.com rdar://104979314 // FIXME: Remove this Quirk if Pinterest decides to trigger this notification from an user gesture (rdar://165745719) - { .match = QuirkMatch::domain("pinterest.com"_s), + { .match = URLMatch::domain("pinterest.com"_s), .behaviors = { ShouldAllowNotificationPermissionWithoutUserGesture } }, - { .match = QuirkMatch::domain("premierleague.com"_s), + { .match = URLMatch::domain("premierleague.com"_s), .behaviors = { // premierleague.com: rdar://123721211 ShouldIgnorePlaysInlineRequirementQuirk, @@ -641,12 +641,12 @@ static constexpr Quirk table[] = { #if PLATFORM(IOS_FAMILY) // ralphlauren.com rdar://55629493 - { .match = QuirkMatch::domain("ralphlauren.com"_s), + { .match = URLMatch::domain("ralphlauren.com"_s), .behaviors = { ShouldIgnoreAriaForFastPathContentObservationCheckQuirk } }, #endif #if ENABLE(VIDEO_PRESENTATION_MODE) || PLATFORM(IOS_FAMILY) - { .match = QuirkMatch::domain("reddit.com"_s), + { .match = URLMatch::domain("reddit.com"_s), .behaviors = { #if ENABLE(VIDEO_PRESENTATION_MODE) // reddit.com: rdar://80550715 @@ -660,19 +660,19 @@ static constexpr Quirk table[] = { .site = QuirkSite::Reddit }, #endif - { .match = QuirkMatch::domain("scribd.com"_s), + { .match = URLMatch::domain("scribd.com"_s), .behaviors = { NeedsReuseLiveRangeForSelectionUpdateQuirk } }, // sfusd.edu: rdar://116292738 - { .match = QuirkMatch::domain("sfusd.edu"_s), + { .match = URLMatch::domain("sfusd.edu"_s), .behaviors = { ShouldBypassAsyncScriptDeferring } }, // sharepoint.com rdar://52116170 - { .match = QuirkMatch::domain("sharepoint.com"_s), + { .match = URLMatch::domain("sharepoint.com"_s), .behaviors = { ShouldAvoidResizingWhenInputViewBoundsChangeQuirk } }, #if PLATFORM(IOS_FAMILY) && ENABLE(META_VIEWPORT) - { .match = QuirkMatch::domain("slack.com"_s), + { .match = URLMatch::domain("slack.com"_s), .behaviors = { // slack.com: rdar://138614711 ShouldIgnoreViewportArgumentsToAvoidEnlargedViewQuirk, @@ -681,7 +681,7 @@ static constexpr Quirk table[] = { } }, #endif - { .match = QuirkMatch::domain("soundcloud.com"_s), + { .match = URLMatch::domain("soundcloud.com"_s), .behaviors = { // soundcloud.com rdar://52915981 ShouldDispatchSimulatedMouseEventsAssumeDefaultPreventedQuirk, @@ -696,12 +696,12 @@ static constexpr Quirk table[] = { #if ENABLE(TOUCH_EVENTS) // soylent.*: rdar://113314067 - { .match = QuirkMatch::anyTopLevelDomain("soylent"_s), + { .match = URLMatch::anyTopLevelDomain("soylent"_s), .behaviors = { ShouldDispatchPointerOutAndLeaveAfterHandlingSyntheticClick } }, #endif // spotify.com rdar://138918575 - { .match = QuirkMatch::host("open.spotify.com"_s), + { .match = URLMatch::host("open.spotify.com"_s), .behaviors = { NeedsBodyScrollbarWidthNoneDisabledQuirk, ShouldAvoidStartingSelectionOnMouseDownOverPointerCursor, @@ -716,20 +716,20 @@ static constexpr Quirk table[] = { #if ENABLE(CONTENT_CHANGE_OBSERVER) // Remove this once rdar://142573562 is resolved. - { .match = QuirkMatch::domain("steampowered.com"_s), + { .match = URLMatch::domain("steampowered.com"_s), .behaviors = { ShouldTreatAddingMouseOutEventListenerAsContentChange } }, #endif #if PLATFORM(IOS_FAMILY) - { .match = QuirkMatch::anyTopLevelDomain("theguardian"_s), + { .match = URLMatch::anyTopLevelDomain("theguardian"_s), .behaviors = { ShouldHideSoftTopScrollEdgeEffectDuringFocusQuirk } }, // theguardian.com rdar://166727225 - { .match = QuirkMatch::anyTopLevelDomain("theguardian"_s).when(documentDomainIs(youTubeEmbedDomains)), + { .match = QuirkURLMatch::embeddedDocumentInTopMatch(URLMatch::anyTopLevelDomain("theguardian"_s), URLMatch::domain(youTubeEmbedDomains)), .behaviors = { NeedsYouTubeEmbedAutoplayQuirk } }, #endif - { .match = QuirkMatch::domain("thesaurus.com"_s), + { .match = URLMatch::domain("thesaurus.com"_s), .behaviors = { #if PLATFORM(IOS_FAMILY) NeedsScriptToEvaluateBeforeRunningScriptFromURLQuirk, @@ -740,7 +740,7 @@ static constexpr Quirk table[] = { }, .site = QuirkSite::Thesaurus }, - { .match = QuirkMatch::domain("tiktok.com"_s), + { .match = URLMatch::domain("tiktok.com"_s), .behaviors = { NeedsTikTokOverflowingContentQuirk, // tiktok.com rdar://174179805 @@ -754,31 +754,31 @@ static constexpr Quirk table[] = { #if PLATFORM(MAC) // trix-editor.org rdar://28242210 - { .match = QuirkMatch::domain("trix-editor.org"_s), + { .match = URLMatch::domain("trix-editor.org"_s), .behaviors = { IsNeverRichlyEditableForTouchBarQuirk } }, #endif #if ENABLE(PICTURE_IN_PICTURE_API) // twitch.tv rdar://102420527 - { .match = QuirkMatch::domain("twitch.tv"_s), + { .match = URLMatch::domain("twitch.tv"_s), .behaviors = { ShouldReportDocumentAsVisibleIfActivePIPQuirk } }, #endif // https://tympanus.net/Tutorials/WebGPUFluid/ does not load (rdar://143839620). - { .match = QuirkMatch::domain("tympanus.net"_s), + { .match = URLMatch::domain("tympanus.net"_s), .behaviors = { ShouldBlockFetchWithNewlineAndLessThan } }, #if ENABLE(MEDIA_SOURCE) // unifi.ui.com rdar://180411019 - { .match = QuirkMatch::domain("ui.com"_s), + { .match = URLMatch::domain("ui.com"_s), .behaviors = { NeedsSupportsProgressMonitoringQuirk } }, #endif // Breaks express checkout on victoriassecret.com (rdar://104818312). - { .match = QuirkMatch::domain("victoriassecret.com"_s), + { .match = URLMatch::domain("victoriassecret.com"_s), .behaviors = { ShouldDisableFetchMetadata } }, - { .match = QuirkMatch::domain("vimeo.com"_s), + { .match = URLMatch::domain("vimeo.com"_s), .behaviors = { // vimeo.com rdar://56996057 MaybeBypassBackForwardCache, @@ -801,13 +801,13 @@ static constexpr Quirk table[] = { #if PLATFORM(IOS_FAMILY) // rdar://116531089 - { .match = QuirkMatch::domain("vimeo.com"_s).when(smallScreen()), + { .match = URLMatch::domain("vimeo.com"_s).when(smallScreen()), .behaviors = { ShouldDisableElementFullscreenQuirk } }, #endif #if ENABLE(TWO_PHASE_CLICKS) // walmart.com: rdar://123734840 - { .match = QuirkMatch::domain("walmart.com"_s), + { .match = URLMatch::domain("walmart.com"_s), .behaviors = { MayNeedToIgnoreContentObservation, }, @@ -816,12 +816,12 @@ static constexpr Quirk table[] = { #if PLATFORM(MAC) // weather.com rdar://139689157 - { .match = QuirkMatch::domain("weather.com"_s), + { .match = URLMatch::domain("weather.com"_s), .behaviors = { NeedsFormControlToBeMouseFocusableQuirk } }, #endif #if PLATFORM(IOS_FAMILY) && ENABLE(DESKTOP_CONTENT_MODE_QUIRKS) - { .match = QuirkMatch::domain("webex.com"_s), + { .match = URLMatch::domain("webex.com"_s), .behaviors = { NeedsScriptToEvaluateBeforeRunningScriptFromURLQuirk, }, @@ -829,10 +829,10 @@ static constexpr Quirk table[] = { #endif // weebly.com rdar://48003980 - { .match = QuirkMatch::domain("weebly.com"_s), + { .match = URLMatch::domain("weebly.com"_s), .behaviors = { ShouldDispatchSyntheticMouseEventsWhenModifyingSelectionQuirk } }, - { .match = QuirkMatch::domain("wikipedia.org"_s), + { .match = URLMatch::domain("wikipedia.org"_s), .behaviors = { // wikipedia.org rdar://54856323 ShouldLayOutAtMinimumWindowWidthWhenIgnoringScalingConstraintsQuirk, @@ -845,20 +845,20 @@ static constexpr Quirk table[] = { // rdar://170412045, https://bugs.webkit.org/show_bug.cgi?id=307933 #if ENABLE(TOUCH_EVENTS) || ENABLE(TOUCH_EVENT_REGIONS) // wix.com rdar://49124313, except while picking a template. - { .match = QuirkMatch::domain("wix.com"_s).exceptWhen(pathStartsWith("/website/templates/"_s)), + { .match = URLMatch::domain("wix.com"_s).exceptWhen(pathStartsWith("/website/templates/"_s)), .behaviors = { ShouldDispatchSimulatedMouseEventsQuirk } }, #endif - { .match = QuirkMatch::domain("workspaces.xyz"_s), + { .match = URLMatch::domain("workspaces.xyz"_s), .behaviors = { ShouldComparareUsedValuesForBorderWidthForTriggeringTransitions } }, #if PLATFORM(MAC) // wpdevelopment.ca rdar://156109518 - { .match = QuirkMatch::domain("wpdevelopment.ca"_s), + { .match = URLMatch::domain("wpdevelopment.ca"_s), .behaviors = { NeedsFormControlToBeMouseFocusableQuirk } }, #endif - { .match = QuirkMatch::domain("x.com"_s), + { .match = URLMatch::domain("x.com"_s), .behaviors = { #if PLATFORM(VISION) // x.com: rdar://132850672 @@ -880,7 +880,7 @@ static constexpr Quirk table[] = { #endif } }, - { .match = QuirkMatch::anyTopLevelDomain("yahoo"_s), + { .match = URLMatch::anyTopLevelDomain("yahoo"_s), .behaviors = { // yahoo.com: rdar://170502516 NeedsYahooVolumeSliderQuirk, @@ -894,11 +894,11 @@ static constexpr Quirk table[] = { #if ENABLE(TEXT_AUTOSIZING) // news.ycombinator.com: rdar://127246368 - { .match = QuirkMatch::host("news.ycombinator.com"_s), + { .match = URLMatch::host("news.ycombinator.com"_s), .behaviors = { ShouldIgnoreTextAutoSizingQuirk } }, #endif - { .match = QuirkMatch::domain("youtube.com"_s), + { .match = URLMatch::domain("youtube.com"_s), .behaviors = { // youtube.com https://bugs.webkit.org/show_bug.cgi?id=195598 HasBrokenEncryptedMediaAPISupportQuirk, @@ -915,65 +915,65 @@ static constexpr Quirk table[] = { #if PLATFORM(COCOA) // Embedded youtube.com players need the caption quirk regardless of the embedding site. - { .match = QuirkMatch::anySite().when(embedded(), documentDomainIs(youTubeEmbedDomains)), + { .match = QuirkURLMatch::embeddedDocument(URLMatch::domain(youTubeEmbedDomains)), .behaviors = { NeedsYouTubeCaptionQuirk } }, #endif #if PLATFORM(IOS_FAMILY) // YouTube.com does not provide AirPlay controls in fullscreen // (Ref: rdar://121471373) - { .match = QuirkMatch::domain("youtube.com"_s).when(smallScreen()), + { .match = URLMatch::domain("youtube.com"_s).when(smallScreen()), .behaviors = { ShouldDisableElementFullscreenQuirk } }, // tiny. (Ref: rdar://121471373, rdar://121473410) - { .match = QuirkMatch::anySite().when(embedded(), documentDomainIs(youTubeEmbedDomains), smallScreen()), + { .match = QuirkURLMatch::embeddedDocument(URLMatch::domain(youTubeEmbedDomains).when(smallScreen())), .behaviors = { ShouldDisableElementFullscreenQuirk } }, - { .match = QuirkMatch::anySite().when(embedded(), documentDomainIs("x.com"_s)), + { .match = QuirkURLMatch::embeddedDocument(URLMatch::domain("x.com"_s)), .behaviors = { ShouldDisableElementFullscreenQuirk } }, // youtube.com rdar://49582231 - { .match = QuirkMatch::host("www.youtube.com"_s), + { .match = URLMatch::host("www.youtube.com"_s), .behaviors = { NeedsYouTubeOverflowScrollQuirk } }, - { .match = QuirkMatch::domain("youtube.com"_s).when(tubularApp()), + { .match = URLMatch::domain("youtube.com"_s).when(tubularApp()), .behaviors = { ShouldSuppressMediaSessionPauseActionOnInterruption } }, #endif #if ENABLE(TWO_PHASE_CLICKS) // www.youtube.com rdar://52361019 - { .match = QuirkMatch::host("www.youtube.com"_s), + { .match = URLMatch::host("www.youtube.com"_s), .behaviors = { NeedsYouTubeMouseOutQuirk } }, #endif #if PLATFORM(VISION) && ENABLE(FULLSCREEN_API) // Lens.app rdar://178769976 - { .match = QuirkMatch::domain("youtube.com"_s).when(lensApp()), + { .match = URLMatch::domain("youtube.com"_s).when(lensApp()), .behaviors = { RequiresUserGestureToPlayInFullscreenQuirk } }, #endif #if ENABLE(MEDIA_RECORDER) && ENABLE(COCOA_WEBM_PLAYER) // zencastr.com rdar://143087016 - { .match = QuirkMatch::domain("zencastr.com"_s), + { .match = URLMatch::domain("zencastr.com"_s), .behaviors = { NeedsLimitedMatroskaSupportQuirk } }, #endif // zillow.com rdar://53103732 - { .match = QuirkMatch::host("www.zillow.com"_s), + { .match = URLMatch::host("www.zillow.com"_s), .behaviors = { ShouldAvoidScrollingWhenFocusedContentIsVisibleQuirk } }, #if PLATFORM(IOS) || PLATFORM(VISION) // zillow.com rdar://110097836 - { .match = QuirkMatch::domain("zillow.com"_s), + { .match = URLMatch::domain("zillow.com"_s), .behaviors = { ShouldSilenceResizeObservers } }, #endif #if PLATFORM(MAC) - { .match = QuirkMatch::domain("zomato.com"_s), + { .match = URLMatch::domain("zomato.com"_s), .behaviors = { NeedsZomatoEmailLoginLabelQuirk } }, #endif - { .match = QuirkMatch::domain("zoom.us"_s), + { .match = URLMatch::domain("zoom.us"_s), .behaviors = { // zoom.com https://bugs.webkit.org/show_bug.cgi?id=223180 ShouldAutoplayWebAudioForArbitraryUserGestureQuirk, @@ -987,6 +987,26 @@ static constexpr Quirk table[] = { } // namespace SiteSpecificQuirks +bool QuirkURLMatch::matches(const URLMatchContext& topContext, const URLMatchContext& documentContext, IsTopDocument isTopDocument) const +{ + switch (m_kind) { + case Kind::TopURL: + return m_match.matches(topContext); + case Kind::EmbeddedDocument: + if (isTopDocument == IsTopDocument::Yes) + return false; + return m_match.matches(documentContext); + case Kind::EmbeddedDocumentInTopURL: + if (isTopDocument == IsTopDocument::Yes) + return false; + ASSERT(m_topMatch); + return m_topMatch->matches(topContext) && m_match.matches(documentContext); + } + + ASSERT_NOT_REACHED(); + return false; +} + void Quirk::apply(QuirksData& quirksData) const { quirksData.activeQuirks.merge(behaviors.bits()); @@ -995,13 +1015,16 @@ void Quirk::apply(QuirksData& quirksData) const quirksData.addSite(*site); } -QuirksData resolveSiteSpecificQuirks(const QuirkMatchContext& context) +QuirksData resolveSiteSpecificQuirks(const URL& topURL, const URL& documentURL, IsTopDocument documentIsTopDocument) { + URLMatchContext topURLContext { topURL }; + URLMatchContext documentURLContext { documentURL }; QuirksData quirksData; for (auto& quirk : SiteSpecificQuirks::table) { - if (quirk.match.matches(context)) + if (quirk.match.matches(topURLContext, documentURLContext, documentIsTopDocument)) quirk.apply(quirksData); } + return quirksData; } diff --git a/Source/WebCore/page/QuirkTable.h b/Source/WebCore/page/QuirkTable.h index 16c98606e2cc..f174782d5a9a 100644 --- a/Source/WebCore/page/QuirkTable.h +++ b/Source/WebCore/page/QuirkTable.h @@ -25,9 +25,9 @@ #pragma once -#include #include #include +#include #include #include @@ -49,14 +49,51 @@ class QuirkBehaviors { QuirkBitSet m_bits; }; +enum class IsTopDocument : bool { No, Yes }; + +class QuirkURLMatch { +public: + constexpr QuirkURLMatch(URLMatch match) + : m_kind(Kind::TopURL) + , m_match(match) + { + } + + static constexpr QuirkURLMatch embeddedDocument(URLMatch match) + { + return QuirkURLMatch { Kind::EmbeddedDocument, match }; + } + + static constexpr QuirkURLMatch embeddedDocumentInTopMatch(URLMatch topMatch, URLMatch documentMatch) + { + return QuirkURLMatch { Kind::EmbeddedDocumentInTopURL, documentMatch, topMatch }; + } + + [[nodiscard]] WEBCORE_EXPORT bool matches(const URLMatchContext& topContext, const URLMatchContext& documentContext, IsTopDocument) const; + +private: + enum class Kind : uint8_t { TopURL, EmbeddedDocument, EmbeddedDocumentInTopURL }; + + constexpr QuirkURLMatch(Kind kind, URLMatch match, std::optional topMatch = std::nullopt) + : m_kind(kind) + , m_match(match) + , m_topMatch(topMatch) + { + } + + Kind m_kind; + URLMatch m_match; + std::optional m_topMatch; +}; + struct Quirk { - QuirkMatch match; + QuirkURLMatch match; QuirkBehaviors behaviors { }; std::optional site { }; void apply(QuirksData&) const; }; -WEBCORE_EXPORT QuirksData resolveSiteSpecificQuirks(const QuirkMatchContext&); +WEBCORE_EXPORT QuirksData resolveSiteSpecificQuirks(const URL& topURL, const URL& documentURL, IsTopDocument); } // namespace WebCore diff --git a/Source/WebCore/page/Quirks.cpp b/Source/WebCore/page/Quirks.cpp index bf44504f6b43..0e25814df2eb 100644 --- a/Source/WebCore/page/Quirks.cpp +++ b/Source/WebCore/page/Quirks.cpp @@ -103,10 +103,6 @@ #include #include -#if PLATFORM(IOS_FAMILY) -#include -#endif - #if PLATFORM(COCOA) #include #endif @@ -2825,33 +2821,6 @@ void Quirks::setTopDocumentURLForTesting(URL&& url) determineRelevantQuirks(); } -bool evaluateQuirkEnvironment(QuirkEnvironment environment) -{ - switch (environment) { - case QuirkEnvironment::SmallScreen: -#if PLATFORM(IOS_FAMILY) - return PAL::currentUserInterfaceIdiomIsSmallScreen(); -#else - return false; -#endif - case QuirkEnvironment::TubularApp: -#if PLATFORM(IOS_FAMILY) - return WTF::IOSApplication::isTubular(); -#else - return false; -#endif - case QuirkEnvironment::LensApp: -#if PLATFORM(IOS_FAMILY) - return WTF::IOSApplication::isLensApp(); -#else - return false; -#endif - } - - ASSERT_NOT_REACHED(); - return false; -} - void Quirks::determineRelevantQuirks() { RELEASE_ASSERT(m_document); @@ -2884,7 +2853,7 @@ void Quirks::determineRelevantQuirks() return; Ref document = *protect(m_document); - m_quirksData.merge(resolveSiteSpecificQuirks({ quirksURL, document->url(), document->isTopDocument() ? IsTopDocument::Yes : IsTopDocument::No })); + m_quirksData.merge(resolveSiteSpecificQuirks(quirksURL, document->url(), document->isTopDocument() ? IsTopDocument::Yes : IsTopDocument::No)); #if ENABLE(FLIP_SCREEN_DIMENSIONS_QUIRKS) // rdar://133423460 diff --git a/Source/WebCore/page/QuirkMatch.cpp b/Source/WebCore/page/URLMatch.cpp similarity index 50% rename from Source/WebCore/page/QuirkMatch.cpp rename to Source/WebCore/page/URLMatch.cpp index 1a8f148d3202..1220ac1c6ab7 100644 --- a/Source/WebCore/page/QuirkMatch.cpp +++ b/Source/WebCore/page/URLMatch.cpp @@ -24,92 +24,113 @@ */ #include "config.h" -#include "QuirkMatch.h" +#include "URLMatch.h" #include "PublicSuffixStore.h" #include "RegistrableDomain.h" +#if PLATFORM(IOS_FAMILY) +#include +#include +#endif + namespace WebCore { -const String& QuirkMatchContext::topRegistrableDomain() const +const String& URLMatchContext::registrableDomain() const { - if (!m_topRegistrableDomain) - m_topRegistrableDomain = RegistrableDomain { m_topURL }.string(); - return *m_topRegistrableDomain; + if (!m_registrableDomain) + m_registrableDomain = RegistrableDomain { m_url }.string(); + return *m_registrableDomain; } -const String& QuirkMatchContext::topDomainWithoutPublicSuffix() const +const String& URLMatchContext::domainWithoutPublicSuffix() const { - if (!m_topDomainWithoutPublicSuffix) - m_topDomainWithoutPublicSuffix = PublicSuffixStore::singleton().domainWithoutPublicSuffix(topRegistrableDomain()); - return *m_topDomainWithoutPublicSuffix; + if (!m_domainWithoutPublicSuffix) + m_domainWithoutPublicSuffix = PublicSuffixStore::singleton().domainWithoutPublicSuffix(registrableDomain()); + return *m_domainWithoutPublicSuffix; } -const String& QuirkMatchContext::documentRegistrableDomain() const +bool evaluateURLEnvironment(URLEnvironment environment) { - if (!m_documentRegistrableDomain) - m_documentRegistrableDomain = RegistrableDomain { m_documentURL }.string(); - return *m_documentRegistrableDomain; + switch (environment) { + case URLEnvironment::SmallScreen: +#if PLATFORM(IOS_FAMILY) + return PAL::currentUserInterfaceIdiomIsSmallScreen(); +#else + return false; +#endif + case URLEnvironment::TubularApp: +#if PLATFORM(IOS_FAMILY) + return WTF::IOSApplication::isTubular(); +#else + return false; +#endif + case URLEnvironment::LensApp: +#if PLATFORM(IOS_FAMILY) + return WTF::IOSApplication::isLensApp(); +#else + return false; +#endif + } + + ASSERT_NOT_REACHED(); + return false; } -bool QuirkMatch::RefinementSet::matchesPathPattern(const URL& topURL) const +bool URLMatch::RefinementSet::matchesPathPattern(const URL& url) const { switch (pathComparison) { case PathComparison::PathContains: - return topURL.path().contains(pathPattern); + return url.path().contains(pathPattern); case PathComparison::PathStartsWith: - return startsWithLettersIgnoringASCIICase(topURL.path(), pathPattern); + return startsWithLettersIgnoringASCIICase(url.path(), pathPattern); case PathComparison::PathOrFragmentContains: - return topURL.path().contains(pathPattern) || topURL.fragmentIdentifier().contains(pathPattern); + return url.path().contains(pathPattern) || url.fragmentIdentifier().contains(pathPattern); } ASSERT_NOT_REACHED(); return false; } -bool QuirkMatch::RefinementSet::matches(const QuirkMatchContext& context) const +bool URLMatch::RefinementSet::matches(const URLMatchContext& context) const { - if (!pathPattern.isNull() && !matchesPathPattern(context.topURL())) - return false; - - if (environment && !evaluateQuirkEnvironment(*environment)) - return false; - - if (requiresEmbeddedDocument && context.isTopDocument()) + if (!pathPattern.isNull() && !matchesPathPattern(context.url())) return false; - if (!documentDomains.isEmpty() && !documentDomains.contains(context.documentRegistrableDomain())) + if (environment && !evaluateURLEnvironment(*environment)) return false; - if (!hosts.isEmpty() && !hosts.contains(context.topHost())) + if (!hosts.isEmpty() && !hosts.contains(context.host())) return false; return true; } -bool QuirkMatch::matchesSite(const QuirkMatchContext& context) const +bool URLMatch::matchesURL(const URLMatchContext& context) const { switch (m_kind) { case Kind::Domain: - return m_patterns.contains(context.topRegistrableDomain()); + return m_patterns.contains(context.registrableDomain()); case Kind::Host: - return m_patterns.contains(context.topHost()); + return m_patterns.contains(context.host()); case Kind::HostOrSubdomainOf: - return m_patterns.containsMatching([&](ASCIILiteral pattern) { return context.topURL().isMatchingDomain(pattern); }); + return m_patterns.containsMatching([&](ASCIILiteral pattern) { + return context.url().isMatchingDomain(pattern); + }); case Kind::AnyTopLevelDomain: - return m_patterns.contains(context.topDomainWithoutPublicSuffix()); - case Kind::AnySite: - // about:, data:, and other URLs without a host are not sites. - return !context.topHost().isEmpty(); + return m_patterns.contains(context.domainWithoutPublicSuffix()); + case Kind::Any: + // about:, data:, and other URLs without a host are never matched. + return !context.host().isEmpty(); } ASSERT_NOT_REACHED(); return false; } -bool QuirkMatch::matches(const QuirkMatchContext& context) const +bool URLMatch::matches(const URLMatchContext& context) const { - if (!matchesSite(context)) [[likely]] + if (!matchesURL(context)) [[likely]] return false; if (!m_refinements.matches(context)) diff --git a/Source/WebCore/page/QuirkMatch.h b/Source/WebCore/page/URLMatch.h similarity index 56% rename from Source/WebCore/page/QuirkMatch.h rename to Source/WebCore/page/URLMatch.h index 7d6c7a17b938..93b8ed676572 100644 --- a/Source/WebCore/page/QuirkMatch.h +++ b/Source/WebCore/page/URLMatch.h @@ -35,62 +35,76 @@ #include #include +// URLMatch is a declarative description of a set of URLs +// +// Every match starts with exactly one static factory, which picks how the URL is +// identified. Each takes a single pattern or a list of them: +// +// domain() the registrable domain, so "youtube.com" also covers +// player.youtube.com but not youtube.co.uk +// host() the exact host, so "docs.google.com" covers nothing else +// hostOrSubdomainOf() the host or any subdomain of it, respecting label boundaries, +// for HTTP-family URLs only +// anyTopLevelDomain() the domain under every public suffix, so "amazon" covers +// amazon.com and amazon.co.uk +// anyURL() every URL with a host, for matches keyed only on refinements +// +// when() then narrows the match with URLRefinement refinements, all of which are ANDed +// together: +// +// URLMatch::anyTopLevelDomain("apple"_s).when(pathStartsWith("/store"_s)) +// +// exceptWhen() takes the same refinements to carve matching URLs back out, so a +// refinement reads identically in either position and its scope is bounded by the call: +// +// URLMatch::domain("wix.com"_s).exceptWhen(pathStartsWith("/website/templates/"_s)) +// + namespace WebCore { -enum class QuirkEnvironment : uint8_t { +enum class URLEnvironment : uint8_t { SmallScreen, TubularApp, LensApp, }; -WEBCORE_EXPORT bool evaluateQuirkEnvironment(QuirkEnvironment); +WEBCORE_EXPORT bool evaluateURLEnvironment(URLEnvironment); -enum class IsTopDocument : bool { No, Yes }; - -class QuirkMatchContext { +class URLMatchContext { public: - QuirkMatchContext(URL topURL, URL documentURL, IsTopDocument isTopDocument) - : m_topURL(WTF::move(topURL)) - , m_documentURL(WTF::move(documentURL)) - , m_isTopDocument(isTopDocument) + URLMatchContext(URL url) + : m_url(WTF::move(url)) { } - const URL& topURL() const LIFETIME_BOUND { return m_topURL; } - - bool isTopDocument() const { return m_isTopDocument == IsTopDocument::Yes; } - - StringView topHost() const LIFETIME_BOUND { return m_topURL.host(); } + const URL& url() const LIFETIME_BOUND { return m_url; } - WEBCORE_EXPORT const String& topRegistrableDomain() const LIFETIME_BOUND; + StringView host() const LIFETIME_BOUND { return m_url.host(); } - WEBCORE_EXPORT const String& topDomainWithoutPublicSuffix() const LIFETIME_BOUND; + WEBCORE_EXPORT const String& domainWithoutPublicSuffix() const LIFETIME_BOUND; - WEBCORE_EXPORT const String& documentRegistrableDomain() const LIFETIME_BOUND; + WEBCORE_EXPORT const String& registrableDomain() const LIFETIME_BOUND; private: - const URL m_topURL; - const URL m_documentURL; - const IsTopDocument m_isTopDocument; - mutable std::optional m_topRegistrableDomain; - mutable std::optional m_topDomainWithoutPublicSuffix; - mutable std::optional m_documentRegistrableDomain; + const URL m_url; + mutable std::optional m_registrableDomain; + mutable std::optional m_domainWithoutPublicSuffix; }; -class QuirkPatternList { +class URLPatternList { public: - constexpr QuirkPatternList() = default; + constexpr URLPatternList() = default; - constexpr QuirkPatternList(ASCIILiteral pattern) + constexpr URLPatternList(ASCIILiteral pattern) : m_single(pattern) { RELEASE_ASSERT_UNDER_CONSTEXPR_CONTEXT(!pattern.isNull()); } - template constexpr QuirkPatternList(const std::array& patterns LIFETIME_BOUND) + template constexpr URLPatternList(const std::array& patterns LIFETIME_BOUND) : m_multiple(patterns) { - static_assert(size, "A quirk pattern list must name at least one pattern."); + static_assert(size, "A URL pattern list must name at least one pattern."); } constexpr bool isEmpty() const { return m_single.isNull() && m_multiple.empty(); } @@ -123,7 +137,7 @@ class QuirkPatternList { std::span m_multiple; }; -namespace QuirkRefinement { +namespace URLRefinement { struct PathContains { ASCIILiteral substring; @@ -137,20 +151,14 @@ struct PathOrFragmentContains { ASCIILiteral substring; }; -struct DocumentDomainIs { - QuirkPatternList domains; -}; - struct HostIs { - QuirkPatternList hosts; + URLPatternList hosts; }; struct EnvironmentIs { - QuirkEnvironment environment; + URLEnvironment environment; }; -struct Embedded { }; - constexpr PathContains pathContains(ASCIILiteral substring) { return { substring }; @@ -166,90 +174,56 @@ constexpr PathOrFragmentContains pathOrFragmentContains(ASCIILiteral substring) return { substring }; } -constexpr DocumentDomainIs documentDomainIs(QuirkPatternList domains) -{ - return { domains }; -} - -constexpr HostIs hostIs(QuirkPatternList hosts) +constexpr HostIs hostIs(URLPatternList hosts) { return { hosts }; } -constexpr Embedded embedded() -{ - return { }; -} - constexpr EnvironmentIs smallScreen() { - return { QuirkEnvironment::SmallScreen }; + return { URLEnvironment::SmallScreen }; } constexpr EnvironmentIs tubularApp() { - return { QuirkEnvironment::TubularApp }; + return { URLEnvironment::TubularApp }; } constexpr EnvironmentIs lensApp() { - return { QuirkEnvironment::LensApp }; + return { URLEnvironment::LensApp }; } -} // namespace QuirkRefinement +} // namespace URLRefinement -// QuirkMatch represents a declarative description of which pages a quirk applies to -// -// Every match starts with exactly one static factory, which picks how the site is -// identified. Each takes a single pattern or a list of them: -// -// domain() the registrable domain, so "youtube.com" also covers -// player.youtube.com but not youtube.co.uk -// host() the exact host, so "docs.google.com" covers nothing else -// hostOrSubdomainOf() the host or any subdomain of it, respecting label boundaries, -// for HTTP-family URLs only -// anyTopLevelDomain() the domain under every public suffix, so "amazon" covers -// amazon.com and amazon.co.uk -// anySite() every page with a host, for quirks keyed only on refinements -// -// when() then narrows the match with QuirkRefinement refinements, all of which are ANDed -// together: -// -// QuirkMatch::anyTopLevelDomain("apple"_s).when(pathStartsWith("/store"_s)) -// -// exceptWhen() takes the same refinements to carve matching pages back out, so a -// refinement reads identically in either position and its scope is bounded by the call: -// -// QuirkMatch::domain("wix.com"_s).exceptWhen(pathStartsWith("/website/templates/"_s)) -// -class QuirkMatch { +class URLMatch { public: - static constexpr QuirkMatch domain(QuirkPatternList domains) + static constexpr URLMatch domain(URLPatternList domains) { - return QuirkMatch { Kind::Domain, domains }; + return URLMatch { Kind::Domain, domains }; } - static constexpr QuirkMatch host(QuirkPatternList hosts) + static constexpr URLMatch host(URLPatternList hosts) { - return QuirkMatch { Kind::Host, hosts }; + return URLMatch { Kind::Host, hosts }; } - static constexpr QuirkMatch hostOrSubdomainOf(QuirkPatternList hosts) + static constexpr URLMatch hostOrSubdomainOf(URLPatternList hosts) { - return QuirkMatch { Kind::HostOrSubdomainOf, hosts }; + return URLMatch { Kind::HostOrSubdomainOf, hosts }; } - static constexpr QuirkMatch anyTopLevelDomain(QuirkPatternList names) + static constexpr URLMatch anyTopLevelDomain(URLPatternList names) { - return QuirkMatch { Kind::AnyTopLevelDomain, names }; + return URLMatch { Kind::AnyTopLevelDomain, names }; } - static constexpr QuirkMatch anySite() + static constexpr URLMatch anyURL() { - return QuirkMatch { Kind::AnySite }; + return URLMatch { Kind::Any }; } - template constexpr QuirkMatch when(Refinements... refinements) && + template constexpr URLMatch when(Refinements... refinements) && { static_assert(sizeof...(refinements), "when() must name at least one refinement to match."); @@ -257,7 +231,7 @@ class QuirkMatch { return WTF::move(*this); } - template constexpr QuirkMatch exceptWhen(Refinements... refinements) && + template constexpr URLMatch exceptWhen(Refinements... refinements) && { static_assert(sizeof...(refinements), "exceptWhen() must name at least one refinement to exclude."); RELEASE_ASSERT_UNDER_CONSTEXPR_CONTEXT(!m_exception); @@ -268,7 +242,7 @@ class QuirkMatch { return WTF::move(*this); } - WEBCORE_EXPORT bool matches(const QuirkMatchContext&) const; + WEBCORE_EXPORT bool matches(const URLMatchContext&) const; private: enum class Kind : uint8_t { @@ -276,7 +250,7 @@ class QuirkMatch { Host, HostOrSubdomainOf, AnyTopLevelDomain, - AnySite, + Any, }; enum class PathComparison : uint8_t { @@ -288,15 +262,13 @@ class QuirkMatch { struct RefinementSet { PathComparison pathComparison { PathComparison::PathContains }; ASCIILiteral pathPattern; - std::optional environment; - QuirkPatternList documentDomains; - QuirkPatternList hosts; - bool requiresEmbeddedDocument { false }; + std::optional environment; + URLPatternList hosts; - bool matches(const QuirkMatchContext&) const; + bool matches(const URLMatchContext&) const; private: - bool matchesPathPattern(const URL& topURL) const; + bool matchesPathPattern(const URL&) const; }; static constexpr void setPathPattern(RefinementSet& set, PathComparison comparison, ASCIILiteral pattern) @@ -306,60 +278,48 @@ class QuirkMatch { set.pathPattern = pattern; } - static constexpr void applyRefinement(RefinementSet& set, QuirkRefinement::PathContains refinement) + static constexpr void applyRefinement(RefinementSet& set, URLRefinement::PathContains refinement) { setPathPattern(set, PathComparison::PathContains, refinement.substring); } - static constexpr void applyRefinement(RefinementSet& set, QuirkRefinement::PathStartsWith refinement) + static constexpr void applyRefinement(RefinementSet& set, URLRefinement::PathStartsWith refinement) { setPathPattern(set, PathComparison::PathStartsWith, refinement.prefix); } - static constexpr void applyRefinement(RefinementSet& set, QuirkRefinement::PathOrFragmentContains refinement) + static constexpr void applyRefinement(RefinementSet& set, URLRefinement::PathOrFragmentContains refinement) { setPathPattern(set, PathComparison::PathOrFragmentContains, refinement.substring); } - static constexpr void applyRefinement(RefinementSet& set, QuirkRefinement::DocumentDomainIs refinement) - { - RELEASE_ASSERT_UNDER_CONSTEXPR_CONTEXT(set.documentDomains.isEmpty()); - set.documentDomains = refinement.domains; - } - - static constexpr void applyRefinement(RefinementSet& set, QuirkRefinement::HostIs refinement) + static constexpr void applyRefinement(RefinementSet& set, URLRefinement::HostIs refinement) { RELEASE_ASSERT_UNDER_CONSTEXPR_CONTEXT(set.hosts.isEmpty()); set.hosts = refinement.hosts; } - static constexpr void applyRefinement(RefinementSet& set, QuirkRefinement::EnvironmentIs refinement) + static constexpr void applyRefinement(RefinementSet& set, URLRefinement::EnvironmentIs refinement) { RELEASE_ASSERT_UNDER_CONSTEXPR_CONTEXT(!set.environment); set.environment = refinement.environment; } - static constexpr void applyRefinement(RefinementSet& set, QuirkRefinement::Embedded) - { - RELEASE_ASSERT_UNDER_CONSTEXPR_CONTEXT(!set.requiresEmbeddedDocument); - set.requiresEmbeddedDocument = true; - } - - constexpr explicit QuirkMatch(Kind kind) + constexpr explicit URLMatch(Kind kind) : m_kind(kind) { } - constexpr QuirkMatch(Kind kind, QuirkPatternList patterns) + constexpr URLMatch(Kind kind, URLPatternList patterns) : m_kind(kind) , m_patterns(patterns) { } - bool matchesSite(const QuirkMatchContext&) const; + bool matchesURL(const URLMatchContext&) const; Kind m_kind; - QuirkPatternList m_patterns; + URLPatternList m_patterns; RefinementSet m_refinements; std::optional m_exception; }; diff --git a/Tools/TestWebKitAPI/CMakeLists.txt b/Tools/TestWebKitAPI/CMakeLists.txt index d15937eefcfe..00958c260e81 100644 --- a/Tools/TestWebKitAPI/CMakeLists.txt +++ b/Tools/TestWebKitAPI/CMakeLists.txt @@ -350,8 +350,8 @@ if (ENABLE_WEBKIT) set(TestWebKit_SOURCES Helpers/Utilities.cpp - Tests/WebCore/QuirkMatch.cpp Tests/WebCore/Quirks.cpp + Tests/WebCore/URLMatch.cpp Tests/WebKit/WKPage/AboutBlankLoad.cpp Tests/WebKit/WKPage/CanHandleRequest.cpp diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj index 863deafa44b3..322ad5ff2263 100644 --- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj +++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj @@ -820,10 +820,10 @@ WebCore/PublicSuffix.cpp, WebCore/PushDatabase.cpp, WebCore/PushMessageCrypto.cpp, - WebCore/QuirkMatch.cpp, WebCore/Quirks.cpp, WebCore/RegionTests.cpp, WebCore/RegistrableDomain.cpp, + WebCore/URLMatch.cpp, WebCore/WellKnownOriginList.cpp, WebCore/RenderStyleChange.cpp, WebCore/SampleMap.cpp, diff --git a/Tools/TestWebKitAPI/Tests/WebCore/QuirkMatch.cpp b/Tools/TestWebKitAPI/Tests/WebCore/QuirkMatch.cpp deleted file mode 100644 index ab59def12ee8..000000000000 --- a/Tools/TestWebKitAPI/Tests/WebCore/QuirkMatch.cpp +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright (C) 2026 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" - -#include -#include -#include -#include - -namespace TestWebKitAPI { - -using WebCore::QuirkEnvironment; -using WebCore::QuirkMatch; -using WebCore::QuirkMatchContext; -using namespace WebCore::QuirkRefinement; - -static bool matchesURL(const QuirkMatch& match, ASCIILiteral urlString) -{ - return match.matches(QuirkMatchContext { URL { urlString }, URL { urlString }, WebCore::IsTopDocument::Yes }); -} - -static bool matchesEmbeddedURL(const QuirkMatch& match, ASCIILiteral topURLString, ASCIILiteral documentURLString) -{ - return match.matches(QuirkMatchContext { URL { topURLString }, URL { documentURLString }, WebCore::IsTopDocument::No }); -} - -static constexpr std::array expediaGroupDomains { "hotels.com"_s, "orbitz.com"_s, "wotif.co.nz"_s }; -static constexpr std::array youTubeEmbedDomains { "youtube.com"_s, "youtube-nocookie.com"_s }; -static constexpr std::array vimeoDomains { "vimeo.com"_s }; -static constexpr std::array excludedNaverHosts { "tv.naver.com"_s, "mail.naver.com"_s, "m.naver.com"_s }; - -TEST(QuirkMatchTest, DomainMatchesRegistrableDomain) -{ - auto match = QuirkMatch::domain("example.com"_s); - - EXPECT_TRUE(matchesURL(match, "https://example.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://www.example.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://deep.sub.example.com/some/path?query#fragment"_s)); - EXPECT_TRUE(matchesURL(match, "http://example.com:8080/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://example.org/"_s)); - EXPECT_FALSE(matchesURL(match, "https://notexample.com/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://example.com.evil.com/"_s)); -} - -TEST(QuirkMatchTest, DomainUnderstandsMultiLabelPublicSuffixes) -{ - auto match = QuirkMatch::domain("bbc.co.uk"_s); - - EXPECT_TRUE(matchesURL(match, "https://www.bbc.co.uk/news"_s)); - EXPECT_TRUE(matchesURL(match, "https://bbc.co.uk/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://bbc.com/"_s)); -} - -TEST(QuirkMatchTest, DomainsMatchesAnyPatternInTheList) -{ - auto match = QuirkMatch::domain(expediaGroupDomains); - - EXPECT_TRUE(matchesURL(match, "https://www.hotels.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://orbitz.com/flights"_s)); - EXPECT_TRUE(matchesURL(match, "https://www.wotif.co.nz/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://www.expedia.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://wotif.com/"_s)); -} - -TEST(QuirkMatchTest, HostMatchesExactHostOnly) -{ - auto match = QuirkMatch::host("docs.google.com"_s); - - EXPECT_TRUE(matchesURL(match, "https://docs.google.com/spreadsheets/d/abc"_s)); - EXPECT_TRUE(matchesURL(match, "https://DOCS.GOOGLE.COM/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://google.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.docs.google.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://sheets.docs.google.com/"_s)); -} - -TEST(QuirkMatchTest, HostOrSubdomainOfRespectsLabelBoundaries) -{ - auto match = QuirkMatch::hostOrSubdomainOf("ceac.state.gov"_s); - - EXPECT_TRUE(matchesURL(match, "https://ceac.state.gov/CEAC/"_s)); - EXPECT_TRUE(matchesURL(match, "https://travel.ceac.state.gov/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://notceac.state.gov/"_s)); - EXPECT_FALSE(matchesURL(match, "https://state.gov/"_s)); - - EXPECT_FALSE(matchesURL(match, "ftp://ceac.state.gov/"_s)); -} - -TEST(QuirkMatchTest, HostOrSubdomainOfCoversShardedHosts) -{ - auto match = QuirkMatch::hostOrSubdomainOf("onedrive.live.com"_s); - - EXPECT_TRUE(matchesURL(match, "https://onedrive.live.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://p123.onedrive.live.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://ONEDRIVE.LIVE.COM/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://myonedrive.live.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://live.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://onedrive.live.com.evil.com/"_s)); -} - -TEST(QuirkMatchTest, AnyTopLevelDomainMatchesEveryPublicSuffix) -{ - auto match = QuirkMatch::anyTopLevelDomain("amazon"_s); - - EXPECT_TRUE(matchesURL(match, "https://www.amazon.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://www.amazon.co.uk/gp/video/"_s)); - EXPECT_TRUE(matchesURL(match, "https://amazon.de/"_s)); - EXPECT_TRUE(matchesURL(match, "https://smile.amazon.com/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://notamazon.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://amazon.com.evil.com/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://amazon.invalidtld/"_s)); -} - -TEST(QuirkMatchTest, PathContainsMatchesAnywhereInThePath) -{ - auto match = QuirkMatch::anyTopLevelDomain("apple"_s).when(pathContains("/retail"_s)); - - EXPECT_TRUE(matchesURL(match, "https://www.apple.com/retail/"_s)); - EXPECT_TRUE(matchesURL(match, "https://www.apple.com/us/retail/store"_s)); - EXPECT_TRUE(matchesURL(match, "https://www.apple.co.uk/retail/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://www.apple.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.apple.com/RETAIL/"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.apple.com/?section=/retail"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.apple.com/#/retail"_s)); - - EXPECT_FALSE(matchesURL(match, "https://www.example.com/retail/"_s)); -} - -TEST(QuirkMatchTest, PathStartsWithIsAnchored) -{ - auto match = QuirkMatch::host("docs.google.com"_s).when(pathStartsWith("/spreadsheets/"_s)); - - EXPECT_TRUE(matchesURL(match, "https://docs.google.com/spreadsheets/d/abc/edit"_s)); - EXPECT_TRUE(matchesURL(match, "https://docs.google.com/SpreadSheets/d/abc"_s)); - - EXPECT_FALSE(matchesURL(match, "https://docs.google.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://docs.google.com/spreadsheets"_s)); - EXPECT_FALSE(matchesURL(match, "https://docs.google.com/a/spreadsheets/d/abc"_s)); -} - -TEST(QuirkMatchTest, PathOrFragmentContainsSearchesBoth) -{ - auto match = QuirkMatch::domain("icloud.com"_s).when(pathOrFragmentContains("mail"_s)); - - EXPECT_TRUE(matchesURL(match, "https://www.icloud.com/mail/"_s)); - EXPECT_TRUE(matchesURL(match, "https://www.icloud.com/#mail"_s)); - - EXPECT_FALSE(matchesURL(match, "https://www.icloud.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.icloud.com/notes/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://www.icloud.com/?app=mail"_s)); -} - -TEST(QuirkMatchTest, EnvironmentIsANDedWithTheSiteMatch) -{ - auto smallScreenOnly = QuirkMatch::domain("youtube.com"_s).when(smallScreen()); - - EXPECT_EQ(matchesURL(smallScreenOnly, "https://www.youtube.com/"_s), WebCore::evaluateQuirkEnvironment(QuirkEnvironment::SmallScreen)); - - EXPECT_FALSE(matchesURL(smallScreenOnly, "https://www.example.com/"_s)); - -#if !PLATFORM(IOS_FAMILY) - EXPECT_FALSE(WebCore::evaluateQuirkEnvironment(QuirkEnvironment::SmallScreen)); - EXPECT_FALSE(WebCore::evaluateQuirkEnvironment(QuirkEnvironment::TubularApp)); - EXPECT_FALSE(WebCore::evaluateQuirkEnvironment(QuirkEnvironment::LensApp)); - - EXPECT_FALSE(matchesURL(smallScreenOnly, "https://www.youtube.com/"_s)); -#endif -} - -TEST(QuirkMatchTest, DocumentDomainIsMatchesEmbeddedDocuments) -{ - auto match = QuirkMatch::anyTopLevelDomain("theguardian"_s).when(documentDomainIs(youTubeEmbedDomains)); - - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://www.youtube.com/embed/abc"_s)); - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.co.uk/film"_s, "https://www.youtube-nocookie.com/embed/abc"_s)); - - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://foo.bar.youtube.com/embed/abc"_s)); - - EXPECT_FALSE(matchesURL(match, "https://www.theguardian.com/film"_s)); - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://vimeo.com/12345"_s)); - - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.example.com/"_s, "https://www.youtube.com/embed/abc"_s)); - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.youtube.com/"_s, "https://www.theguardian.com/film"_s)); -} - -TEST(QuirkMatchTest, DocumentDomainIsAcceptsASinglePattern) -{ - auto match = QuirkMatch::anySite().when(embedded(), documentDomainIs("x.com"_s)); - - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://x.com/i/status/123"_s)); - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.example.com/"_s, "https://platform.x.com/embed/Tweet.html"_s)); - - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.example.com/"_s, "https://vimeo.com/12345"_s)); - EXPECT_FALSE(matchesURL(match, "https://x.com/i/status/123"_s)); -} - -TEST(QuirkMatchTest, AnySiteWithOnlyIfEmbeddedMatchesEmbedsAnywhereButNeverTheTopDocument) -{ - auto match = QuirkMatch::anySite().when(embedded(), documentDomainIs(youTubeEmbedDomains)); - - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://www.youtube.com/embed/abc"_s)); - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.example.com/"_s, "https://www.youtube-nocookie.com/embed/abc"_s)); - - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.example.com/"_s, "https://vimeo.com/12345"_s)); - // youtube.com at the top level, and youtube.com embedded in itself, are both the top document. - EXPECT_FALSE(matchesURL(match, "https://www.youtube.com/watch?v=abc"_s)); -} - -TEST(QuirkMatchTest, AnySiteMatchesEverySiteWithoutFurtherRefinement) -{ - auto match = QuirkMatch::anySite(); - - EXPECT_TRUE(matchesURL(match, "https://www.example.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://webkit.org/"_s)); - EXPECT_FALSE(matchesURL(match, "about:blank"_s)); -} - -TEST(QuirkMatchTest, ExceptWhenCarvesOutPagesOfAMatchedSite) -{ - auto match = QuirkMatch::domain("wix.com"_s).exceptWhen(pathStartsWith("/website/templates/"_s)); - - EXPECT_TRUE(matchesURL(match, "https://www.wix.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://www.wix.com/website/other"_s)); - // The exception reuses pathStartsWith(), so it is anchored the same way. - EXPECT_TRUE(matchesURL(match, "https://www.wix.com/x/website/templates/blank"_s)); - - EXPECT_FALSE(matchesURL(match, "https://www.wix.com/website/templates/"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.wix.com/website/templates/blank"_s)); - - // The exception only ever narrows: a page it describes on another site is still no match. - EXPECT_FALSE(matchesURL(match, "https://www.example.com/website/other"_s)); -} - -TEST(QuirkMatchTest, ExceptWhenCarvesOutHostsOfAMatchedSite) -{ - auto match = QuirkMatch::hostOrSubdomainOf("naver.com"_s).exceptWhen(hostIs(excludedNaverHosts)); - - EXPECT_TRUE(matchesURL(match, "https://naver.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://news.naver.com/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://tv.naver.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://m.naver.com/"_s)); - - EXPECT_TRUE(matchesURL(match, "https://sub.tv.naver.com/"_s)); -} - -TEST(QuirkMatchTest, ExceptWhenCarvesOutASingleHost) -{ - auto match = QuirkMatch::hostOrSubdomainOf("naver.com"_s).exceptWhen(hostIs("tv.naver.com"_s)); - - EXPECT_TRUE(matchesURL(match, "https://naver.com/"_s)); - EXPECT_TRUE(matchesURL(match, "https://news.naver.com/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://tv.naver.com/"_s)); - - EXPECT_TRUE(matchesURL(match, "https://sub.tv.naver.com/"_s)); -} - -TEST(QuirkMatchTest, HostIsNarrowsAMatchToOneHost) -{ - auto match = QuirkMatch::domain("naver.com"_s).when(hostIs("tv.naver.com"_s)); - - EXPECT_TRUE(matchesURL(match, "https://tv.naver.com/"_s)); - - EXPECT_FALSE(matchesURL(match, "https://naver.com/"_s)); - EXPECT_FALSE(matchesURL(match, "https://news.naver.com/"_s)); -} - -TEST(QuirkMatchTest, ExceptWhenTakesEveryRefinementIncludingEmbeddedDocuments) -{ - auto match = QuirkMatch::domain("theguardian.com"_s).exceptWhen(embedded(), documentDomainIs(vimeoDomains)); - - EXPECT_TRUE(matchesURL(match, "https://www.theguardian.com/film"_s)); - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://www.youtube.com/embed/abc"_s)); - - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://vimeo.com/12345"_s)); -} - -TEST(QuirkMatchTest, ExceptWhenAndTheMatchKeepSeparateRefinements) -{ - auto match = QuirkMatch::domain("example.com"_s).when(pathStartsWith("/app"_s)).exceptWhen(pathStartsWith("/app/legacy"_s)); - - EXPECT_TRUE(matchesURL(match, "https://www.example.com/app/main"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.example.com/other"_s)); - EXPECT_FALSE(matchesURL(match, "https://www.example.com/app/legacy/page"_s)); -} - -TEST(QuirkMatchTest, ExceptWhenRequiresEveryRefinementToExclude) -{ - auto match = QuirkMatch::domain("example.com"_s).exceptWhen(pathStartsWith("/embed"_s), embedded()); - - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.example.com/embed/abc"_s, "https://vimeo.com/12345"_s)); - - EXPECT_TRUE(matchesURL(match, "https://www.example.com/embed/abc"_s)); - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.example.com/other"_s, "https://vimeo.com/12345"_s)); - - EXPECT_FALSE(matchesURL(match, "https://webkit.org/"_s)); -} - -TEST(QuirkMatchTest, MatchesIgnoreTheDocumentURLByDefault) -{ - auto match = QuirkMatch::domain("theguardian.com"_s); - - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.com/film"_s, "https://www.youtube.com/embed/abc"_s)); - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.youtube.com/"_s, "https://www.theguardian.com/film"_s)); -} - -TEST(QuirkMatchTest, RefinementsOfDifferentKindsAreAllANDed) -{ - auto match = QuirkMatch::anyTopLevelDomain("theguardian"_s).when(pathStartsWith("/film/"_s), documentDomainIs(youTubeEmbedDomains)); - - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.com/film/2026/trailer"_s, "https://www.youtube.com/embed/abc"_s)); - EXPECT_TRUE(matchesEmbeddedURL(match, "https://www.theguardian.co.uk/film/2026/trailer"_s, "https://www.youtube-nocookie.com/embed/abc"_s)); - - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.example.com/film/2026/trailer"_s, "https://www.youtube.com/embed/abc"_s)); - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.theguardian.com/news/2026/story"_s, "https://www.youtube.com/embed/abc"_s)); - EXPECT_FALSE(matchesEmbeddedURL(match, "https://www.theguardian.com/film/2026/trailer"_s, "https://vimeo.com/12345"_s)); -} - -TEST(QuirkMatchTest, EnvironmentStacksWithAPathRefinement) -{ - auto match = QuirkMatch::domain("youtube.com"_s).when(pathStartsWith("/shorts/"_s), smallScreen()); - - EXPECT_EQ(matchesURL(match, "https://www.youtube.com/shorts/abc"_s), WebCore::evaluateQuirkEnvironment(QuirkEnvironment::SmallScreen)); - EXPECT_FALSE(matchesURL(match, "https://www.youtube.com/watch?v=abc"_s)); -} - -TEST(QuirkMatchTest, ContextDerivesValuesFromTheRightURL) -{ - URL topURL { "https://www.bbc.co.uk/news?live=1#top"_s }; - URL documentURL { "https://player.youtube-nocookie.com/embed/abc"_s }; - QuirkMatchContext context { topURL, documentURL, WebCore::IsTopDocument::Yes }; - - EXPECT_EQ(context.topURL(), topURL); - EXPECT_EQ(context.topHost(), "www.bbc.co.uk"_s); - EXPECT_EQ(context.topRegistrableDomain(), "bbc.co.uk"_s); - EXPECT_EQ(context.topDomainWithoutPublicSuffix(), "bbc"_s); - EXPECT_EQ(context.documentRegistrableDomain(), "youtube-nocookie.com"_s); -} - -TEST(QuirkMatchTest, ContextCachesDerivedValues) -{ - URL url { "https://www.example.com/"_s }; - QuirkMatchContext context { url, url, WebCore::IsTopDocument::Yes }; - - EXPECT_EQ(&context.topRegistrableDomain(), &context.topRegistrableDomain()); - EXPECT_EQ(&context.topDomainWithoutPublicSuffix(), &context.topDomainWithoutPublicSuffix()); - EXPECT_EQ(&context.documentRegistrableDomain(), &context.documentRegistrableDomain()); -} - -TEST(QuirkMatchTest, HostsWithoutAPublicSuffixFallBackToTheHost) -{ - EXPECT_TRUE(matchesURL(QuirkMatch::domain("localhost"_s), "http://localhost:8080/"_s)); - EXPECT_TRUE(matchesURL(QuirkMatch::domain("127.0.0.1"_s), "http://127.0.0.1/"_s)); - EXPECT_TRUE(matchesURL(QuirkMatch::anyTopLevelDomain("127.0.0.1"_s), "http://127.0.0.1/"_s)); -} - -TEST(QuirkMatchTest, URLsWithoutAHostMatchNothing) -{ - for (auto urlString : { "about:blank"_s, "data:text/html,hello"_s, ""_s }) { - URL url { urlString }; - QuirkMatchContext context { url, url, WebCore::IsTopDocument::Yes }; - - EXPECT_FALSE(QuirkMatch::domain("example.com"_s).matches(context)); - EXPECT_FALSE(QuirkMatch::host("example.com"_s).matches(context)); - EXPECT_FALSE(QuirkMatch::hostOrSubdomainOf("example.com"_s).matches(context)); - EXPECT_FALSE(QuirkMatch::anyTopLevelDomain("example"_s).matches(context)); - } -} - -} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/Tests/WebCore/Quirks.cpp b/Tools/TestWebKitAPI/Tests/WebCore/Quirks.cpp index 0f6fcb8a4e83..5e6498d6a8a5 100644 --- a/Tools/TestWebKitAPI/Tests/WebCore/Quirks.cpp +++ b/Tools/TestWebKitAPI/Tests/WebCore/Quirks.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -48,9 +49,85 @@ static std::optional customUserAgentFor(ASCIILiteral urlString) static WebCore::QuirksData resolveQuirksForTopURL(ASCIILiteral urlString) { - return WebCore::resolveSiteSpecificQuirks({ URL { urlString }, URL { urlString }, WebCore::IsTopDocument::Yes }); + return WebCore::resolveSiteSpecificQuirks(URL { urlString }, URL { urlString }, WebCore::IsTopDocument::Yes); } +static bool matchesTopURL(const WebCore::QuirkURLMatch& match, ASCIILiteral urlString) +{ + return match.matches(WebCore::URLMatchContext { URL { urlString } }, WebCore::URLMatchContext { URL { urlString } }, WebCore::IsTopDocument::Yes); +} + +static bool matchesEmbeddedDocument(const WebCore::QuirkURLMatch& match, ASCIILiteral topURLString, ASCIILiteral documentURLString) +{ + return match.matches(WebCore::URLMatchContext { URL { topURLString } }, WebCore::URLMatchContext { URL { documentURLString } }, WebCore::IsTopDocument::No); +} + +static constexpr std::array youTubeEmbedDomains { "youtube.com"_s, "youtube-nocookie.com"_s }; + +TEST_F(QuirksTest, TopURLMatchIgnoresTheDocumentURL) +{ + WebCore::QuirkURLMatch match = WebCore::URLMatch::domain("theguardian.com"_s); + + EXPECT_TRUE(matchesTopURL(match, "https://www.theguardian.com/film"_s)); + + EXPECT_TRUE(matchesEmbeddedDocument(match, "https://www.theguardian.com/film"_s, "https://www.youtube.com/embed/abc"_s)); + + EXPECT_FALSE(matchesEmbeddedDocument(match, "https://www.youtube.com/"_s, "https://www.theguardian.com/film"_s)); +} + +TEST_F(QuirksTest, EmbeddedDocumentMatchesTheDocumentURLNotTheTopURL) +{ + auto match = WebCore::QuirkURLMatch::embeddedDocument(WebCore::URLMatch::domain(youTubeEmbedDomains)); + + EXPECT_TRUE(matchesEmbeddedDocument(match, "https://www.theguardian.com/film"_s, "https://www.youtube.com/embed/abc"_s)); + EXPECT_TRUE(matchesEmbeddedDocument(match, "https://www.example.com/"_s, "https://www.youtube-nocookie.com/embed/abc"_s)); + EXPECT_TRUE(matchesEmbeddedDocument(match, "https://www.example.com/"_s, "https://foo.bar.youtube.com/embed/abc"_s)); + + EXPECT_FALSE(matchesEmbeddedDocument(match, "https://www.example.com/"_s, "https://vimeo.com/12345"_s)); + + EXPECT_FALSE(matchesEmbeddedDocument(match, "https://www.youtube.com/watch?v=abc"_s, "https://vimeo.com/12345"_s)); + + EXPECT_FALSE(matchesTopURL(match, "https://www.youtube.com/watch?v=abc"_s)); +} + +TEST_F(QuirksTest, EmbeddedDocumentInTopMatchRequiresBothURLsToMatch) +{ + auto match = WebCore::QuirkURLMatch::embeddedDocumentInTopMatch(WebCore::URLMatch::anyTopLevelDomain("theguardian"_s), WebCore::URLMatch::domain(youTubeEmbedDomains)); + + EXPECT_TRUE(matchesEmbeddedDocument(match, "https://www.theguardian.com/film"_s, "https://www.youtube.com/embed/abc"_s)); + EXPECT_TRUE(matchesEmbeddedDocument(match, "https://www.theguardian.co.uk/film"_s, "https://www.youtube-nocookie.com/embed/abc"_s)); + + EXPECT_FALSE(matchesEmbeddedDocument(match, "https://www.example.com/"_s, "https://www.youtube.com/embed/abc"_s)); + EXPECT_FALSE(matchesEmbeddedDocument(match, "https://www.theguardian.com/film"_s, "https://vimeo.com/12345"_s)); + EXPECT_FALSE(matchesTopURL(match, "https://www.theguardian.com/film"_s)); +} + +TEST_F(QuirksTest, EmbeddedMatchesNeverApplyToTheTopDocument) +{ + auto match = WebCore::QuirkURLMatch::embeddedDocumentInTopMatch(WebCore::URLMatch::anyURL(), WebCore::URLMatch::domain("youtube.com"_s)); + + EXPECT_TRUE(matchesEmbeddedDocument(match, "https://www.example.com/"_s, "https://www.youtube.com/embed/abc"_s)); + EXPECT_FALSE(matchesTopURL(match, "https://www.youtube.com/watch?v=abc"_s)); +} + +#if PLATFORM(COCOA) +static WebCore::QuirksData resolveQuirksForEmbeddedDocument(ASCIILiteral topURLString, ASCIILiteral documentURLString) +{ + return WebCore::resolveSiteSpecificQuirks(URL { topURLString }, URL { documentURLString }, WebCore::IsTopDocument::No); +} + +TEST_F(QuirksTest, EmbeddedQuirksResolveFromTheDocumentURL) +{ + using SiteSpecificQuirk = WebCore::SiteSpecificQuirk; + + EXPECT_TRUE(resolveQuirksForEmbeddedDocument("https://www.example.com/"_s, "https://www.youtube.com/embed/abc"_s).quirkIsEnabled(SiteSpecificQuirk::NeedsYouTubeCaptionQuirk)); + + EXPECT_FALSE(resolveQuirksForEmbeddedDocument("https://www.example.com/"_s, "https://vimeo.com/12345"_s).quirkIsEnabled(SiteSpecificQuirk::NeedsYouTubeCaptionQuirk)); + + EXPECT_FALSE(resolveQuirksForTopURL("https://www.example.com/"_s).quirkIsEnabled(SiteSpecificQuirk::NeedsYouTubeCaptionQuirk)); +} +#endif + TEST_F(QuirksTest, SiteSpecificQuirksResolveWithoutADocument) { using SiteSpecificQuirk = WebCore::SiteSpecificQuirk; diff --git a/Tools/TestWebKitAPI/Tests/WebCore/URLMatch.cpp b/Tools/TestWebKitAPI/Tests/WebCore/URLMatch.cpp new file mode 100644 index 000000000000..04b4bd17fe26 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebCore/URLMatch.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include + +namespace TestWebKitAPI { + +using WebCore::URLEnvironment; +using WebCore::URLMatch; +using WebCore::URLMatchContext; +using namespace WebCore::URLRefinement; + +static bool matchesURL(const URLMatch& match, ASCIILiteral urlString) +{ + return match.matches(URLMatchContext { URL { urlString } }); +} + +static constexpr std::array expediaGroupDomains { "hotels.com"_s, "orbitz.com"_s, "wotif.co.nz"_s }; +static constexpr std::array excludedNaverHosts { "tv.naver.com"_s, "mail.naver.com"_s, "m.naver.com"_s }; + +TEST(URLMatchTest, DomainMatchesRegistrableDomain) +{ + auto match = URLMatch::domain("example.com"_s); + + EXPECT_TRUE(matchesURL(match, "https://example.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.example.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://deep.sub.example.com/some/path?query#fragment"_s)); + EXPECT_TRUE(matchesURL(match, "http://example.com:8080/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://example.org/"_s)); + EXPECT_FALSE(matchesURL(match, "https://notexample.com/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://example.com.evil.com/"_s)); +} + +TEST(URLMatchTest, DomainUnderstandsMultiLabelPublicSuffixes) +{ + auto match = URLMatch::domain("bbc.co.uk"_s); + + EXPECT_TRUE(matchesURL(match, "https://www.bbc.co.uk/news"_s)); + EXPECT_TRUE(matchesURL(match, "https://bbc.co.uk/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://bbc.com/"_s)); +} + +TEST(URLMatchTest, DomainsMatchesAnyPatternInTheList) +{ + auto match = URLMatch::domain(expediaGroupDomains); + + EXPECT_TRUE(matchesURL(match, "https://www.hotels.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://orbitz.com/flights"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.wotif.co.nz/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://www.expedia.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://wotif.com/"_s)); +} + +TEST(URLMatchTest, HostMatchesExactHostOnly) +{ + auto match = URLMatch::host("docs.google.com"_s); + + EXPECT_TRUE(matchesURL(match, "https://docs.google.com/spreadsheets/d/abc"_s)); + EXPECT_TRUE(matchesURL(match, "https://DOCS.GOOGLE.COM/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://google.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.docs.google.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://sheets.docs.google.com/"_s)); +} + +TEST(URLMatchTest, HostOrSubdomainOfRespectsLabelBoundaries) +{ + auto match = URLMatch::hostOrSubdomainOf("ceac.state.gov"_s); + + EXPECT_TRUE(matchesURL(match, "https://ceac.state.gov/CEAC/"_s)); + EXPECT_TRUE(matchesURL(match, "https://travel.ceac.state.gov/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://notceac.state.gov/"_s)); + EXPECT_FALSE(matchesURL(match, "https://state.gov/"_s)); + + EXPECT_FALSE(matchesURL(match, "ftp://ceac.state.gov/"_s)); +} + +TEST(URLMatchTest, HostOrSubdomainOfCoversShardedHosts) +{ + auto match = URLMatch::hostOrSubdomainOf("onedrive.live.com"_s); + + EXPECT_TRUE(matchesURL(match, "https://onedrive.live.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://p123.onedrive.live.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://ONEDRIVE.LIVE.COM/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://myonedrive.live.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://live.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://onedrive.live.com.evil.com/"_s)); +} + +TEST(URLMatchTest, AnyTopLevelDomainMatchesEveryPublicSuffix) +{ + auto match = URLMatch::anyTopLevelDomain("amazon"_s); + + EXPECT_TRUE(matchesURL(match, "https://www.amazon.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.amazon.co.uk/gp/video/"_s)); + EXPECT_TRUE(matchesURL(match, "https://amazon.de/"_s)); + EXPECT_TRUE(matchesURL(match, "https://smile.amazon.com/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://notamazon.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://amazon.com.evil.com/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://amazon.invalidtld/"_s)); +} + +TEST(URLMatchTest, PathContainsMatchesAnywhereInThePath) +{ + auto match = URLMatch::anyTopLevelDomain("apple"_s).when(pathContains("/retail"_s)); + + EXPECT_TRUE(matchesURL(match, "https://www.apple.com/retail/"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.apple.com/us/retail/store"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.apple.co.uk/retail/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://www.apple.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.apple.com/RETAIL/"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.apple.com/?section=/retail"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.apple.com/#/retail"_s)); + + EXPECT_FALSE(matchesURL(match, "https://www.example.com/retail/"_s)); +} + +TEST(URLMatchTest, PathStartsWithIsAnchored) +{ + auto match = URLMatch::host("docs.google.com"_s).when(pathStartsWith("/spreadsheets/"_s)); + + EXPECT_TRUE(matchesURL(match, "https://docs.google.com/spreadsheets/d/abc/edit"_s)); + EXPECT_TRUE(matchesURL(match, "https://docs.google.com/SpreadSheets/d/abc"_s)); + + EXPECT_FALSE(matchesURL(match, "https://docs.google.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://docs.google.com/spreadsheets"_s)); + EXPECT_FALSE(matchesURL(match, "https://docs.google.com/a/spreadsheets/d/abc"_s)); +} + +TEST(URLMatchTest, PathOrFragmentContainsSearchesBoth) +{ + auto match = URLMatch::domain("icloud.com"_s).when(pathOrFragmentContains("mail"_s)); + + EXPECT_TRUE(matchesURL(match, "https://www.icloud.com/mail/"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.icloud.com/#mail"_s)); + + EXPECT_FALSE(matchesURL(match, "https://www.icloud.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.icloud.com/notes/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://www.icloud.com/?app=mail"_s)); +} + +TEST(URLMatchTest, EnvironmentIsANDedWithTheSiteMatch) +{ + auto smallScreenOnly = URLMatch::domain("youtube.com"_s).when(smallScreen()); + + EXPECT_EQ(matchesURL(smallScreenOnly, "https://www.youtube.com/"_s), WebCore::evaluateURLEnvironment(URLEnvironment::SmallScreen)); + + EXPECT_FALSE(matchesURL(smallScreenOnly, "https://www.example.com/"_s)); + +#if !PLATFORM(IOS_FAMILY) + EXPECT_FALSE(WebCore::evaluateURLEnvironment(URLEnvironment::SmallScreen)); + EXPECT_FALSE(WebCore::evaluateURLEnvironment(URLEnvironment::TubularApp)); + EXPECT_FALSE(WebCore::evaluateURLEnvironment(URLEnvironment::LensApp)); + + EXPECT_FALSE(matchesURL(smallScreenOnly, "https://www.youtube.com/"_s)); +#endif +} + +TEST(URLMatchTest, AnyURLMatchesEverySiteWithoutFurtherRefinement) +{ + auto match = URLMatch::anyURL(); + + EXPECT_TRUE(matchesURL(match, "https://www.example.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://webkit.org/"_s)); + EXPECT_FALSE(matchesURL(match, "about:blank"_s)); +} + +TEST(URLMatchTest, ExceptWhenCarvesOutPagesOfAMatchedSite) +{ + auto match = URLMatch::domain("wix.com"_s).exceptWhen(pathStartsWith("/website/templates/"_s)); + + EXPECT_TRUE(matchesURL(match, "https://www.wix.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.wix.com/website/other"_s)); + EXPECT_TRUE(matchesURL(match, "https://www.wix.com/x/website/templates/blank"_s)); + + EXPECT_FALSE(matchesURL(match, "https://www.wix.com/website/templates/"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.wix.com/website/templates/blank"_s)); + + EXPECT_FALSE(matchesURL(match, "https://www.example.com/website/other"_s)); +} + +TEST(URLMatchTest, ExceptWhenCarvesOutHostsOfAMatchedSite) +{ + auto match = URLMatch::hostOrSubdomainOf("naver.com"_s).exceptWhen(hostIs(excludedNaverHosts)); + + EXPECT_TRUE(matchesURL(match, "https://naver.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://news.naver.com/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://tv.naver.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://m.naver.com/"_s)); + + EXPECT_TRUE(matchesURL(match, "https://sub.tv.naver.com/"_s)); +} + +TEST(URLMatchTest, ExceptWhenCarvesOutASingleHost) +{ + auto match = URLMatch::hostOrSubdomainOf("naver.com"_s).exceptWhen(hostIs("tv.naver.com"_s)); + + EXPECT_TRUE(matchesURL(match, "https://naver.com/"_s)); + EXPECT_TRUE(matchesURL(match, "https://news.naver.com/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://tv.naver.com/"_s)); + + EXPECT_TRUE(matchesURL(match, "https://sub.tv.naver.com/"_s)); +} + +TEST(URLMatchTest, HostIsNarrowsAMatchToOneHost) +{ + auto match = URLMatch::domain("naver.com"_s).when(hostIs("tv.naver.com"_s)); + + EXPECT_TRUE(matchesURL(match, "https://tv.naver.com/"_s)); + + EXPECT_FALSE(matchesURL(match, "https://naver.com/"_s)); + EXPECT_FALSE(matchesURL(match, "https://news.naver.com/"_s)); +} + +TEST(URLMatchTest, ExceptWhenAndTheMatchKeepSeparateRefinements) +{ + auto match = URLMatch::domain("example.com"_s).when(pathStartsWith("/app"_s)).exceptWhen(pathStartsWith("/app/legacy"_s)); + + EXPECT_TRUE(matchesURL(match, "https://www.example.com/app/main"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.example.com/other"_s)); + EXPECT_FALSE(matchesURL(match, "https://www.example.com/app/legacy/page"_s)); +} + +TEST(URLMatchTest, EnvironmentStacksWithAPathRefinement) +{ + auto match = URLMatch::domain("youtube.com"_s).when(pathStartsWith("/shorts/"_s), smallScreen()); + + EXPECT_EQ(matchesURL(match, "https://www.youtube.com/shorts/abc"_s), WebCore::evaluateURLEnvironment(URLEnvironment::SmallScreen)); + EXPECT_FALSE(matchesURL(match, "https://www.youtube.com/watch?v=abc"_s)); +} + +TEST(URLMatchTest, HostsWithoutAPublicSuffixFallBackToTheHost) +{ + EXPECT_TRUE(matchesURL(URLMatch::domain("localhost"_s), "http://localhost:8080/"_s)); + EXPECT_TRUE(matchesURL(URLMatch::domain("127.0.0.1"_s), "http://127.0.0.1/"_s)); + EXPECT_TRUE(matchesURL(URLMatch::anyTopLevelDomain("127.0.0.1"_s), "http://127.0.0.1/"_s)); +} + +TEST(URLMatchTest, URLsWithoutAHostMatchNothing) +{ + for (auto urlString : { "about:blank"_s, "data:text/html,hello"_s, ""_s }) { + URL url { urlString }; + URLMatchContext context { url }; + + EXPECT_FALSE(URLMatch::domain("example.com"_s).matches(context)); + EXPECT_FALSE(URLMatch::host("example.com"_s).matches(context)); + EXPECT_FALSE(URLMatch::hostOrSubdomainOf("example.com"_s).matches(context)); + EXPECT_FALSE(URLMatch::anyTopLevelDomain("example"_s).matches(context)); + } +} + +TEST(URLMatchTest, ContextDerivesValuesFromItsURL) +{ + URL url { "https://www.bbc.co.uk/news?live=1#top"_s }; + URLMatchContext context { url }; + + EXPECT_EQ(context.url(), url); + EXPECT_EQ(context.host(), "www.bbc.co.uk"_s); + EXPECT_EQ(context.registrableDomain(), "bbc.co.uk"_s); + EXPECT_EQ(context.domainWithoutPublicSuffix(), "bbc"_s); +} + +TEST(URLMatchTest, ContextCachesDerivedValues) +{ + URL url { "https://www.example.com/"_s }; + URLMatchContext context { url }; + + EXPECT_EQ(&context.registrableDomain(), &context.registrableDomain()); + EXPECT_EQ(&context.domainWithoutPublicSuffix(), &context.domainWithoutPublicSuffix()); +} + +} // namespace TestWebKitAPI From 37966e503a143192cb90918fb1d8af70ef42d9a3 Mon Sep 17 00:00:00 2001 From: Brent Fulgham Date: Fri, 28 Aug 2026 14:54:10 -0700 Subject: [PATCH 072/103] [Interop 2026] Correct two Dialogs and Popover failures after WPT sync https://bugs.webkit.org/show_bug.cgi?id=322854 rdar://186097567 Unreviewed re-sync with WPT after landing upstream fix. I forgot to push a local fix upstream: https://github.com/web-platform-tests/wpt/commit/aa609de82b6914e5308cb01b2fc0828b23bc2336 That has now been corrected, so resyncing locally: Upstream commit: https://github.com/web-platform-tests/wpt/commit/073a34f030ad68af980d7a627e829900b003961b * LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup.html: * LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html: Canonical link: https://commits.webkit.org/320080@main --- .../dialog-open-movebefore-setup-expected.txt | 2 +- .../the-dialog-element/dialog-open-movebefore-setup.html | 1 + .../the-dialog-element/inert-svg-hittest-expected.txt | 2 +- .../the-dialog-element/inert-svg-hittest.html | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup-expected.txt index 6b2936d43dc5..909cef0ac37e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup-expected.txt @@ -1,3 +1,3 @@ -FAIL reparenting a dialog should not cause it to move in the open dialogs list promise_test: Unhandled rejection with value: object "TypeError: new_parent.moveBefore is not a function. (In 'new_parent.moveBefore(dialog, null)', 'new_parent.moveBefore' is undefined)" +PASS reparenting a dialog should not cause it to move in the open dialogs list diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup.html b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup.html index 5e90784ecaba..ded9d19a9bac 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup.html +++ b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-movebefore-setup.html @@ -1,3 +1,4 @@ + moveBefore should not re-run dialog setup steps diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest-expected.txt index 9645053880cf..6675d0a40a2e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest-expected.txt @@ -1,4 +1,4 @@ PASS Hit-testing doesn't reach contents of an inert SVG -FAIL Hit-testing can reach contents of a no longer inert SVG assert_true: target is active expected true got false +PASS Hit-testing can reach contents of a no longer inert SVG diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html index 579aca777551..c5b64cdba115 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html +++ b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html @@ -43,6 +43,7 @@ .pointerMove(wrapperRect.x + 1, wrapperRect.y + 1, { origin: "viewport" }) .pointerDown() .send(); + this.add_cleanup(() => test_driver.click(document.body)); assert_false(target.matches(":active"), "target is not active"); assert_false(target.matches(":hover"), "target is not hovered"); @@ -61,6 +62,7 @@ .pointerMove(0, 0, { origin: wrapper }) .pointerDown() .send(); + this.add_cleanup(() => test_driver.click(document.body)); assert_true(target.matches(":active"), "target is active"); assert_true(reachedTarget, "target got event"); From 1458014dcd4499f3fbf200aa18c711c7f8d5fc70 Mon Sep 17 00:00:00 2001 From: Ahmad Saleem Date: Fri, 28 Aug 2026 15:08:18 -0700 Subject: [PATCH 073/103] HTMLVideoElement::player() is called and protected redundantly in several accessors https://bugs.webkit.org/show_bug.cgi?id=322677 Reviewed by Chris Dumez. Several HTMLVideoElement accessors called player() two-to-four times, each protect(player()) constructing a fresh RefPtr (ref/deref churn) on hot paths. Cache one RefPtr player = this->player() per function and reuse it. This also normalizes webkitDecodedFrameCount()/webkitDroppedFrameCount(), which previously dereferenced the raw pointer without protecting it. No change in behavior. * Source/WebCore/html/HTMLVideoElement.cpp: (WebCore::HTMLVideoElement::supportsAcceleratedRendering const): (WebCore::HTMLVideoElement::supportsFullscreen const): (WebCore::HTMLVideoElement::videoWidth const): (WebCore::HTMLVideoElement::videoHeight const): (WebCore::HTMLVideoElement::webkitDecodedFrameCount const): (WebCore::HTMLVideoElement::webkitDroppedFrameCount const): Canonical link: https://commits.webkit.org/320081@main --- Source/WebCore/html/HTMLVideoElement.cpp | 36 ++++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Source/WebCore/html/HTMLVideoElement.cpp b/Source/WebCore/html/HTMLVideoElement.cpp index 28380c070376..c78dde641434 100644 --- a/Source/WebCore/html/HTMLVideoElement.cpp +++ b/Source/WebCore/html/HTMLVideoElement.cpp @@ -158,7 +158,8 @@ void HTMLVideoElement::acceleratedRenderingStateChanged() bool HTMLVideoElement::supportsAcceleratedRendering() const { - return RefPtr { player() } && protect(player())->supportsAcceleratedRendering(); + RefPtr player = this->player(); + return player && player->supportsAcceleratedRendering(); } void HTMLVideoElement::mediaPlayerRenderingModeChanged() @@ -251,21 +252,22 @@ void HTMLVideoElement::attributeChanged(const QualifiedName& name, const AtomStr bool HTMLVideoElement::supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenMode videoFullscreenMode) const { - if (!player()) + RefPtr player = this->player(); + if (!player) return false; - + if (videoFullscreenMode == HTMLMediaElementEnums::VideoFullscreenModePictureInPicture) { if (!mediaSession().allowsPictureInPicture()) return false; - if (!protect(player())->supportsPictureInPicture()) + if (!player->supportsPictureInPicture()) return false; } RefPtr page = document().page(); - if (!page) + if (!page) return false; - if (!protect(player())->supportsFullscreen()) + if (!player->supportsFullscreen()) return false; #if HAVE(AVEXPERIENCECONTROLLER) @@ -289,7 +291,7 @@ bool HTMLVideoElement::supportsFullscreen(HTMLMediaElementEnums::VideoFullscreen return true; #endif - if (!protect(player())->hasVideo()) + if (!player->hasVideo()) return false; return page->chrome().client().supportsVideoFullscreen(videoFullscreenMode); @@ -306,16 +308,18 @@ void HTMLVideoElement::requestFullscreen(FullscreenOptions&&, RefPtrplayer(); + if (!player) return 0; - return clampToUnsigned(protect(player())->naturalSize().width()); + return clampToUnsigned(player->naturalSize().width()); } unsigned HTMLVideoElement::videoHeight() const { - if (!player()) + RefPtr player = this->player(); + if (!player) return 0; - return clampToUnsigned(protect(player())->naturalSize().height()); + return clampToUnsigned(player->naturalSize().height()); } void HTMLVideoElement::scheduleResizeEvent(const FloatSize& naturalSize) @@ -516,18 +520,20 @@ void HTMLVideoElement::didMoveToNewDocument(Document& oldDocument, Document& new #if ENABLE(MEDIA_STATISTICS) unsigned HTMLVideoElement::webkitDecodedFrameCount() const { - if (!player()) + RefPtr player = this->player(); + if (!player) return 0; - return player()->decodedFrameCount(); + return player->decodedFrameCount(); } unsigned HTMLVideoElement::webkitDroppedFrameCount() const { - if (!player()) + RefPtr player = this->player(); + if (!player) return 0; - return player()->droppedFrameCount(); + return player->droppedFrameCount(); } #endif From 77a6c78d0dcecbc33602cfcd69f2469fb89185b7 Mon Sep 17 00:00:00 2001 From: Ahmad Saleem Date: Fri, 28 Aug 2026 15:28:08 -0700 Subject: [PATCH 074/103] HTMLMediaElement::isSafeToLoadURL() computes isIPAddressDisallowed() twice https://bugs.webkit.org/show_bug.cgi?id=322678 Reviewed by Chris Dumez. isSafeToLoadURL() called isIPAddressDisallowed(url) once in the guard condition and again when selecting the error message to log. That function parses the URL host via IPAddress::fromString() on each call, so hoist the result into a local bool and reuse it. * Source/WebCore/html/HTMLMediaElement.cpp: (WebCore::HTMLMediaElement::isSafeToLoadURL const): Canonical link: https://commits.webkit.org/320082@main --- Source/WebCore/html/HTMLMediaElement.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp index 1320a44d8315..87a4a6b048a4 100644 --- a/Source/WebCore/html/HTMLMediaElement.cpp +++ b/Source/WebCore/html/HTMLMediaElement.cpp @@ -2923,12 +2923,13 @@ bool HTMLMediaElement::isSafeToLoadURL(const URL& url, InvalidURLAction actionIf return false; } - if (!portAllowed(url) || isIPAddressDisallowed(url)) { + bool ipAddressDisallowed = isIPAddressDisallowed(url); + if (ipAddressDisallowed || !portAllowed(url)) { if (actionIfInvalid == InvalidURLAction::Complain) { if (frame) FrameLoader::reportBlockedLoadFailed(*frame, url); if (shouldLog) { - if (isIPAddressDisallowed(url)) + if (ipAddressDisallowed) ERROR_LOG(LOGIDENTIFIER, url , " was rejected because the address not allowed"); else ERROR_LOG(LOGIDENTIFIER, url , " was rejected because the port is not allowed"); From 1e5465e49a028df7828885037fd0652891f20e00 Mon Sep 17 00:00:00 2001 From: Yusuke Suzuki Date: Fri, 28 Aug 2026 15:45:04 -0700 Subject: [PATCH 075/103] [JSC] ArrayProfile read / update does not need a lock https://bugs.webkit.org/show_bug.cgi?id=322771 rdar://186034745 Reviewed by Yijia Huang. Reading and updating ArrayProfile is fine to be racy as it is just a source of prediction and we are not reading complicated data from the stored Structure. They can be read freely from the concurrent compiler unlike reading butterfly etc. This means that we do not need to have a lock around ArrayProfile. As sizeof(ArrayProfile) is just 16 bytes, in DFG compiler, unless we would like to update the data, we just copy the entire 16 bytes and read the content. * Source/JavaScriptCore/bytecode/ArrayProfile.h: (JSC::ArrayProfile::mayBeLargeTypedArray const): (JSC::ArrayProfile::mayBeResizableOrGrowableSharedTypedArray const): (JSC::ArrayProfile::observedArrayModes const): (JSC::ArrayProfile::mayInterceptIndexedAccesses const): (JSC::ArrayProfile::mayStoreToHole const): (JSC::ArrayProfile::outOfBounds const): (JSC::ArrayProfile::usesOriginalArrayStructures const): (JSC::ArrayProfile::mayBeRegExpMatchesArray const): * Source/JavaScriptCore/bytecode/CodeBlock.cpp: (JSC::CodeBlock::getArrayProfile): * Source/JavaScriptCore/bytecode/CodeBlock.h: * Source/JavaScriptCore/dfg/DFGArrayMode.cpp: (JSC::DFG::ArrayMode::fromObserved): * Source/JavaScriptCore/dfg/DFGArrayMode.h: (JSC::DFG::ArrayMode::speculationFromProfile): (JSC::DFG::ArrayMode::withSpeculationFromProfile const): (JSC::DFG::ArrayMode::withProfile const): * Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp: (JSC::DFG::ByteCodeParser::getArrayMode): (JSC::DFG::ByteCodeParser::profiledArrayMayBeRegExpMatchesArray): * Source/JavaScriptCore/dfg/DFGFixupPhase.cpp: (JSC::DFG::FixupPhase::fixupNode): (JSC::DFG::FixupPhase::attemptToMakeGetArrayLength): * Source/JavaScriptCore/dfg/DFGOSRExit.cpp: (JSC::DFG::OSRExit::compileExit): * Source/JavaScriptCore/ftl/FTLOSRExitCompiler.cpp: (JSC::FTL::compileStub): Canonical link: https://commits.webkit.org/320083@main --- Source/JavaScriptCore/bytecode/ArrayProfile.h | 21 +++---- Source/JavaScriptCore/bytecode/CodeBlock.cpp | 2 +- Source/JavaScriptCore/bytecode/CodeBlock.h | 2 +- Source/JavaScriptCore/dfg/DFGArrayMode.cpp | 62 +++++++++---------- Source/JavaScriptCore/dfg/DFGArrayMode.h | 22 +++---- .../JavaScriptCore/dfg/DFGByteCodeParser.cpp | 24 +++---- Source/JavaScriptCore/dfg/DFGFixupPhase.cpp | 19 +++--- Source/JavaScriptCore/dfg/DFGOSRExit.cpp | 2 +- .../JavaScriptCore/ftl/FTLOSRExitCompiler.cpp | 2 +- 9 files changed, 71 insertions(+), 85 deletions(-) diff --git a/Source/JavaScriptCore/bytecode/ArrayProfile.h b/Source/JavaScriptCore/bytecode/ArrayProfile.h index 1df9ca6c1a31..6dca562c9f06 100644 --- a/Source/JavaScriptCore/bytecode/ArrayProfile.h +++ b/Source/JavaScriptCore/bytecode/ArrayProfile.h @@ -25,7 +25,6 @@ #pragma once -#include #include #include @@ -228,9 +227,9 @@ class ArrayProfile { static constexpr uint64_t s_smallTypedArrayMaxLength = std::numeric_limits::max(); void setMayBeLargeTypedArray() { m_arrayProfileFlags.add(ArrayProfileFlag::MayBeLargeTypedArray); } - bool mayBeLargeTypedArray(const ConcurrentJSLocker&) const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayBeLargeTypedArray); } + bool mayBeLargeTypedArray() const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayBeLargeTypedArray); } - bool mayBeResizableOrGrowableSharedTypedArray(const ConcurrentJSLocker&) const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayBeResizableOrGrowableSharedTypedArray); } + bool mayBeResizableOrGrowableSharedTypedArray() const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayBeResizableOrGrowableSharedTypedArray); } StructureID* addressOfSpeculationFailureStructureID() LIFETIME_BOUND { return &m_speculationFailureStructureID; } ArrayModes* addressOfArrayModes() LIFETIME_BOUND { return &m_observedArrayModes; } @@ -252,15 +251,15 @@ class ArrayProfile { void observeArrayMode(ArrayModes mode) { m_observedArrayModes |= mode; } void NODELETE observeIndexedRead(JSCell*, unsigned index); - ArrayModes observedArrayModes(const ConcurrentJSLocker&) const { return m_observedArrayModes; } - bool mayInterceptIndexedAccesses(const ConcurrentJSLocker&) const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayInterceptIndexedAccesses);; } - - bool mayStoreToHole(const ConcurrentJSLocker&) const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayStoreHole); } - bool outOfBounds(const ConcurrentJSLocker&) const { return m_arrayProfileFlags.contains(ArrayProfileFlag::OutOfBounds); } - - bool usesOriginalArrayStructures(const ConcurrentJSLocker&) const { return !m_arrayProfileFlags.contains(ArrayProfileFlag::UsesNonOriginalArrayStructures); } + ArrayModes observedArrayModes() const { return m_observedArrayModes; } + bool mayInterceptIndexedAccesses() const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayInterceptIndexedAccesses); } + + bool mayStoreToHole() const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayStoreHole); } + bool outOfBounds() const { return m_arrayProfileFlags.contains(ArrayProfileFlag::OutOfBounds); } + + bool usesOriginalArrayStructures() const { return !m_arrayProfileFlags.contains(ArrayProfileFlag::UsesNonOriginalArrayStructures); } - bool mayBeRegExpMatchesArray(const ConcurrentJSLocker&) const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayBeRegExpMatchesArray); } + bool mayBeRegExpMatchesArray() const { return m_arrayProfileFlags.contains(ArrayProfileFlag::MayBeRegExpMatchesArray); } CString briefDescription(CodeBlock*); CString briefDescriptionWithoutUpdating(); diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp index ba85a3e5c433..b0328b642ab0 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp @@ -2922,7 +2922,7 @@ void CodeBlock::didFailFTLCompilation() #endif -ArrayProfile* CodeBlock::getArrayProfile(const ConcurrentJSLocker&, BytecodeIndex bytecodeIndex) +ArrayProfile* CodeBlock::getArrayProfile(BytecodeIndex bytecodeIndex) { auto instruction = instructions().at(bytecodeIndex); diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.h b/Source/JavaScriptCore/bytecode/CodeBlock.h index 5d2ea929d6c0..af3733149bf8 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.h +++ b/Source/JavaScriptCore/bytecode/CodeBlock.h @@ -451,7 +451,7 @@ class CodeBlock : public JSCell { bool NODELETE couldTakeSpecialArithFastCase(BytecodeIndex bytecodeOffset); - ArrayProfile* NODELETE getArrayProfile(const ConcurrentJSLocker&, BytecodeIndex); + ArrayProfile* NODELETE getArrayProfile(BytecodeIndex); // Exception handling support diff --git a/Source/JavaScriptCore/dfg/DFGArrayMode.cpp b/Source/JavaScriptCore/dfg/DFGArrayMode.cpp index 3d34454eac5b..0601017db462 100644 --- a/Source/JavaScriptCore/dfg/DFGArrayMode.cpp +++ b/Source/JavaScriptCore/dfg/DFGArrayMode.cpp @@ -36,10 +36,10 @@ namespace JSC { namespace DFG { -ArrayMode ArrayMode::fromObserved(const ConcurrentJSLocker& locker, ArrayProfile* profile, Array::Action action, bool makeSafe) +ArrayMode ArrayMode::fromObserved(ArrayProfile profile, Array::Action action, bool makeSafe) { Array::Class nonArray; - if (profile->usesOriginalArrayStructures(locker)) + if (profile.usesOriginalArrayStructures()) nonArray = Array::OriginalNonArray; else nonArray = Array::NonArray; @@ -63,27 +63,27 @@ ArrayMode ArrayMode::fromObserved(const ConcurrentJSLocker& locker, ArrayProfile else converts = Array::AsIs; - return ArrayMode(type, isArray, converts, action).withProfile(locker, profile, makeSafe); + return ArrayMode(type, isArray, converts, action).withProfile(profile, makeSafe); }; - ArrayModes observed = profile->observedArrayModes(locker); + ArrayModes observed = profile.observedArrayModes(); switch (observed) { case 0: return ArrayMode(Array::Unprofiled); case asArrayModesIgnoringTypedArrays(NonArray): - if (action == Array::Write && !profile->mayInterceptIndexedAccesses(locker)) + if (action == Array::Write && !profile.mayInterceptIndexedAccesses()) return ArrayMode(Array::SelectUsingArguments, nonArray, Array::OutOfBounds, Array::Convert, action); - return ArrayMode(Array::SelectUsingPredictions, nonArray, action).withSpeculationFromProfile(locker, profile, makeSafe); + return ArrayMode(Array::SelectUsingPredictions, nonArray, action).withSpeculationFromProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(ArrayWithUndecided): if (action == Array::Write) return ArrayMode(Array::SelectUsingArguments, Array::Array, Array::OutOfBounds, Array::Convert, action); - return ArrayMode(Array::Undecided, Array::Array, Array::OutOfBounds, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Undecided, Array::Array, Array::OutOfBounds, Array::AsIs, action).withProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(NonArray) | asArrayModesIgnoringTypedArrays(ArrayWithUndecided): - if (action == Array::Write && !profile->mayInterceptIndexedAccesses(locker)) + if (action == Array::Write && !profile.mayInterceptIndexedAccesses()) return ArrayMode(Array::SelectUsingArguments, Array::PossiblyArray, Array::OutOfBounds, Array::Convert, action); - return ArrayMode(Array::SelectUsingPredictions, action).withSpeculationFromProfile(locker, profile, makeSafe); + return ArrayMode(Array::SelectUsingPredictions, action).withSpeculationFromProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(NonArrayWithInt32): case asArrayModesIgnoringTypedArrays(ArrayWithInt32): @@ -113,52 +113,52 @@ ArrayMode ArrayMode::fromObserved(const ConcurrentJSLocker& locker, ArrayProfile return handleContiguousModes(Array::Contiguous, observed); case asArrayModesIgnoringTypedArrays(NonArrayWithArrayStorage): - return ArrayMode(Array::ArrayStorage, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::ArrayStorage, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(NonArrayWithSlowPutArrayStorage): case asArrayModesIgnoringTypedArrays(NonArrayWithArrayStorage) | asArrayModesIgnoringTypedArrays(NonArrayWithSlowPutArrayStorage): - return ArrayMode(Array::SlowPutArrayStorage, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::SlowPutArrayStorage, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(ArrayWithArrayStorage): - return ArrayMode(Array::ArrayStorage, Array::Array, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::ArrayStorage, Array::Array, Array::AsIs, action).withProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(ArrayWithSlowPutArrayStorage): case asArrayModesIgnoringTypedArrays(ArrayWithArrayStorage) | asArrayModesIgnoringTypedArrays(ArrayWithSlowPutArrayStorage): - return ArrayMode(Array::SlowPutArrayStorage, Array::Array, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::SlowPutArrayStorage, Array::Array, Array::AsIs, action).withProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(NonArrayWithArrayStorage) | asArrayModesIgnoringTypedArrays(ArrayWithArrayStorage): - return ArrayMode(Array::ArrayStorage, Array::PossiblyArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::ArrayStorage, Array::PossiblyArray, Array::AsIs, action).withProfile(profile, makeSafe); case asArrayModesIgnoringTypedArrays(NonArrayWithSlowPutArrayStorage) | asArrayModesIgnoringTypedArrays(ArrayWithSlowPutArrayStorage): case asArrayModesIgnoringTypedArrays(NonArrayWithArrayStorage) | asArrayModesIgnoringTypedArrays(ArrayWithArrayStorage) | asArrayModesIgnoringTypedArrays(NonArrayWithSlowPutArrayStorage) | asArrayModesIgnoringTypedArrays(ArrayWithSlowPutArrayStorage): - return ArrayMode(Array::SlowPutArrayStorage, Array::PossiblyArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::SlowPutArrayStorage, Array::PossiblyArray, Array::AsIs, action).withProfile(profile, makeSafe); case Int8ArrayMode: - return ArrayMode(Array::Int8Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Int8Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Int16ArrayMode: - return ArrayMode(Array::Int16Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Int16Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Int32ArrayMode: - return ArrayMode(Array::Int32Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Int32Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Uint8ArrayMode: - return ArrayMode(Array::Uint8Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Uint8Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Uint8ClampedArrayMode: - return ArrayMode(Array::Uint8ClampedArray, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Uint8ClampedArray, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Uint16ArrayMode: - return ArrayMode(Array::Uint16Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Uint16Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Uint32ArrayMode: - return ArrayMode(Array::Uint32Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Uint32Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Float16ArrayMode: - return ArrayMode(Array::Float16Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Float16Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Float32ArrayMode: - return ArrayMode(Array::Float32Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Float32Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case Float64ArrayMode: - return ArrayMode(Array::Float64Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Float64Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case BigInt64ArrayMode: - return ArrayMode(Array::BigInt64Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::BigInt64Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); case BigUint64ArrayMode: - return ArrayMode(Array::BigUint64Array, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::BigUint64Array, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); default: // If we have seen multiple TypedArray types, or a TypedArray and non-typed array, it doesn't make sense to try to convert the object since you can't convert typed arrays. if (observed & ALL_TYPED_ARRAY_MODES) - return ArrayMode(Array::Generic, nonArray, Array::AsIs, action).withProfile(locker, profile, makeSafe); + return ArrayMode(Array::Generic, nonArray, Array::AsIs, action).withProfile(profile, makeSafe); - if ((observed & asArrayModesIgnoringTypedArrays(NonArray)) && profile->mayInterceptIndexedAccesses(locker)) - return ArrayMode(Array::SelectUsingPredictions).withSpeculationFromProfile(locker, profile, makeSafe); + if ((observed & asArrayModesIgnoringTypedArrays(NonArray)) && profile.mayInterceptIndexedAccesses()) + return ArrayMode(Array::SelectUsingPredictions).withSpeculationFromProfile(profile, makeSafe); Array::Type type; Array::Class arrayClass; @@ -185,7 +185,7 @@ ArrayMode ArrayMode::fromObserved(const ConcurrentJSLocker& locker, ArrayProfile else arrayClass = Array::PossiblyArray; - return ArrayMode(type, arrayClass, Array::Convert, action).withProfile(locker, profile, makeSafe); + return ArrayMode(type, arrayClass, Array::Convert, action).withProfile(profile, makeSafe); } } diff --git a/Source/JavaScriptCore/dfg/DFGArrayMode.h b/Source/JavaScriptCore/dfg/DFGArrayMode.h index 45f4d92ce9f5..051e6ad0f2a3 100644 --- a/Source/JavaScriptCore/dfg/DFGArrayMode.h +++ b/Source/JavaScriptCore/dfg/DFGArrayMode.h @@ -209,7 +209,7 @@ class ArrayMode { return ArrayMode(word); } - static ArrayMode fromObserved(const ConcurrentJSLocker&, ArrayProfile*, Array::Action, bool makeSafe); + static ArrayMode fromObserved(ArrayProfile, Array::Action, bool makeSafe); ArrayMode withType(Array::Type type) const { @@ -246,31 +246,31 @@ class ArrayMode { return ArrayMode(type(), arrayClass, speculation(), conversion(), action(), mayBeLargeTypedArray(), mayBeResizableOrGrowableSharedTypedArray()); } - static Array::Speculation speculationFromProfile(const ConcurrentJSLocker& locker, ArrayProfile* profile, bool makeSafe) + static Array::Speculation speculationFromProfile(ArrayProfile profile, bool makeSafe) { if (makeSafe) return Array::OutOfBounds; - else if (profile->mayStoreToHole(locker)) + else if (profile.mayStoreToHole()) return Array::ToHole; else return Array::InBounds; } - ArrayMode withSpeculationFromProfile(const ConcurrentJSLocker& locker, ArrayProfile* profile, bool makeSafe) const + ArrayMode withSpeculationFromProfile(ArrayProfile profile, bool makeSafe) const { - return withSpeculation(speculationFromProfile(locker, profile, makeSafe)); + return withSpeculation(speculationFromProfile(profile, makeSafe)); } - ArrayMode withProfile(const ConcurrentJSLocker& locker, ArrayProfile* profile, bool makeSafe) const + ArrayMode withProfile(ArrayProfile profile, bool makeSafe) const { Array::Class myArrayClass; if (isJSArray()) { - if (profile->usesOriginalArrayStructures(locker) && benefitsFromOriginalArray()) { + if (profile.usesOriginalArrayStructures() && benefitsFromOriginalArray()) { switch (type()) { case Array::Int32: case Array::Double: case Array::Contiguous: { - ArrayModes arrayModes = profile->observedArrayModes(locker); + ArrayModes arrayModes = profile.observedArrayModes(); if (hasSeenCopyOnWriteArray(arrayModes) && !hasSeenWritableArray(arrayModes)) myArrayClass = Array::OriginalCopyOnWriteArray; else if (!hasSeenCopyOnWriteArray(arrayModes) && hasSeenWritableArray(arrayModes)) @@ -293,11 +293,9 @@ class ArrayMode { } else myArrayClass = arrayClass(); - Array::Speculation speculation = speculationFromProfile(locker, profile, makeSafe); + Array::Speculation speculation = speculationFromProfile(profile, makeSafe); - bool mayBeLargeTypedArray = profile->mayBeLargeTypedArray(locker); - bool mayBeResizableOrGrowableSharedTypedArray = profile->mayBeResizableOrGrowableSharedTypedArray(locker); - return withArrayClassAndSpeculation(myArrayClass, speculation, mayBeLargeTypedArray, mayBeResizableOrGrowableSharedTypedArray); + return withArrayClassAndSpeculation(myArrayClass, speculation, profile.mayBeLargeTypedArray(), profile.mayBeResizableOrGrowableSharedTypedArray()); } static constexpr SpeculatedType unusedIndexSpeculatedType = SpecInt32Only; diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index 64109e410eee..508b2ed808ee 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -1340,34 +1340,26 @@ class ByteCodeParser { ArrayMode getArrayMode(Array::Action action) { CodeBlock* codeBlock = m_inlineStackTop->m_profiledBlock; - ConcurrentJSLocker locker(codeBlock->m_lock); - ArrayProfile* profile = codeBlock->getArrayProfile(locker, codeBlock->bytecodeIndex(m_currentInstruction)); + ArrayProfile* profile = codeBlock->getArrayProfile(codeBlock->bytecodeIndex(m_currentInstruction)); if (!profile) return { }; - return getArrayMode(locker, *profile, action); + return getArrayMode(*profile, action); } - ArrayMode getArrayMode(ArrayProfile& profile, Array::Action action) + ArrayMode getArrayMode(ArrayProfile& liveProfile, Array::Action action) { - ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); - return getArrayMode(locker, profile, action); - } - - ArrayMode getArrayMode(const ConcurrentJSLocker& locker, ArrayProfile& profile, Array::Action action) - { - profile.computeUpdatedPrediction(m_inlineStackTop->m_profiledBlock); - bool makeSafe = profile.outOfBounds(locker); - return ArrayMode::fromObserved(locker, &profile, action, makeSafe); + liveProfile.computeUpdatedPrediction(m_inlineStackTop->m_profiledBlock); + ArrayProfile profile = liveProfile; + return ArrayMode::fromObserved(profile, action, profile.outOfBounds()); } bool profiledArrayMayBeRegExpMatchesArray() { CodeBlock* codeBlock = m_inlineStackTop->m_profiledBlock; - ConcurrentJSLocker locker(codeBlock->m_lock); - ArrayProfile* profile = codeBlock->getArrayProfile(locker, codeBlock->bytecodeIndex(m_currentInstruction)); + ArrayProfile* profile = codeBlock->getArrayProfile(codeBlock->bytecodeIndex(m_currentInstruction)); if (!profile) return false; - return profile->mayBeRegExpMatchesArray(locker); + return profile->mayBeRegExpMatchesArray(); } Node* makeSafe(Node* node) diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp index 717c02b4a3a7..7594e85ca1d8 100644 --- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp @@ -1373,9 +1373,8 @@ class FixupPhase : public Phase { ArrayModes arrayModes = 0; { CodeBlock* profiledBlock = m_graph.baselineCodeBlockFor(node->origin.semantic); - ConcurrentJSLocker locker(profiledBlock->m_lock); - if (ArrayProfile* arrayProfile = profiledBlock->getArrayProfile(locker, node->origin.semantic.bytecodeIndex())) - arrayModes = arrayProfile->observedArrayModes(locker); + if (ArrayProfile* arrayProfile = profiledBlock->getArrayProfile(node->origin.semantic.bytecodeIndex())) + arrayModes = arrayProfile->observedArrayModes(); } auto info = refineArrayModesForMultiGetByVal(node, arrayModes); if (!info) @@ -1564,9 +1563,8 @@ class FixupPhase : public Phase { ArrayModes arrayModes = 0; { CodeBlock* profiledBlock = m_graph.baselineCodeBlockFor(node->origin.semantic); - ConcurrentJSLocker locker(profiledBlock->m_lock); - if (ArrayProfile* arrayProfile = profiledBlock->getArrayProfile(locker, node->origin.semantic.bytecodeIndex())) - arrayModes = arrayProfile->observedArrayModes(locker); + if (ArrayProfile* arrayProfile = profiledBlock->getArrayProfile(node->origin.semantic.bytecodeIndex())) + arrayModes = arrayProfile->observedArrayModes(); } if (auto result = refineArrayModesForMultiPutByVal(node, arrayModes)) { if (m_graph.hasExitSite(node->origin.semantic, OutOfBounds)) { @@ -5175,11 +5173,10 @@ class FixupPhase : public Phase { ArrayMode arrayMode = ArrayMode(Array::SelectUsingPredictions, Array::Read); { CodeBlock* profiledBlock = m_graph.baselineCodeBlockFor(node->origin.semantic); - ConcurrentJSLocker locker(profiledBlock->m_lock); - ArrayProfile* arrayProfile = profiledBlock->getArrayProfile(locker, node->origin.semantic.bytecodeIndex()); - if (arrayProfile) { - arrayProfile->computeUpdatedPrediction(profiledBlock); - arrayMode = ArrayMode::fromObserved(locker, arrayProfile, Array::Read, false); + ArrayProfile* liveProfile = profiledBlock->getArrayProfile(node->origin.semantic.bytecodeIndex()); + if (liveProfile) { + liveProfile->computeUpdatedPrediction(profiledBlock); + arrayMode = ArrayMode::fromObserved(*liveProfile, Array::Read, false); if (arrayMode.type() == Array::Unprofiled) { // For normal array operations, it makes sense to treat Unprofiled // accesses as ForceExit and get more data rather than using diff --git a/Source/JavaScriptCore/dfg/DFGOSRExit.cpp b/Source/JavaScriptCore/dfg/DFGOSRExit.cpp index 67c56dae3717..a637b93e2114 100644 --- a/Source/JavaScriptCore/dfg/DFGOSRExit.cpp +++ b/Source/JavaScriptCore/dfg/DFGOSRExit.cpp @@ -325,7 +325,7 @@ void OSRExit::compileExit(CCallHelpers& jit, VM& vm, const OSRExit& exit, const CodeOrigin codeOrigin = exit.m_codeOriginForExitProfile; CodeBlock* codeBlock = jit.baselineCodeBlockFor(codeOrigin); - if (ArrayProfile* arrayProfile = codeBlock->getArrayProfile(ConcurrentJSLocker(codeBlock->m_lock), codeOrigin.bytecodeIndex())) { + if (ArrayProfile* arrayProfile = codeBlock->getArrayProfile(codeOrigin.bytecodeIndex())) { GPRReg usedRegister; if (exit.m_jsValueSource.isAddress()) usedRegister = exit.m_jsValueSource.base(); diff --git a/Source/JavaScriptCore/ftl/FTLOSRExitCompiler.cpp b/Source/JavaScriptCore/ftl/FTLOSRExitCompiler.cpp index 78a6333a0270..0324ff8c29d1 100644 --- a/Source/JavaScriptCore/ftl/FTLOSRExitCompiler.cpp +++ b/Source/JavaScriptCore/ftl/FTLOSRExitCompiler.cpp @@ -264,7 +264,7 @@ static void compileStub(VM& vm, unsigned exitID, JITCode* jitCode, OSRExit& exit if (exit.m_kind == BadCache || exit.m_kind == BadIndexingType) { CodeOrigin codeOrigin = exit.m_codeOriginForExitProfile; CodeBlock* codeBlock = jit.baselineCodeBlockFor(codeOrigin); - if (ArrayProfile* arrayProfile = codeBlock->getArrayProfile(ConcurrentJSLocker(codeBlock->m_lock), codeOrigin.bytecodeIndex())) { + if (ArrayProfile* arrayProfile = codeBlock->getArrayProfile(codeOrigin.bytecodeIndex())) { jit.move(CCallHelpers::TrustedImmPtr(arrayProfile), GPRInfo::regT3); jit.load32(MacroAssembler::Address(GPRInfo::regT0, JSCell::structureIDOffset()), GPRInfo::regT1); jit.store32(GPRInfo::regT1, CCallHelpers::Address(GPRInfo::regT3, ArrayProfile::offsetOfSpeculationFailureStructureID())); From a346ccb94edad6d8d4cae280e93c4dcdaac7dcc9 Mon Sep 17 00:00:00 2001 From: Phinehas Fuachie Date: Fri, 28 Aug 2026 15:46:43 -0700 Subject: [PATCH 076/103] All subsequent videos played after the first one auto exit fullscreen back to inline https://bugs.webkit.org/show_bug.cgi?id=322768 rdar://174807377 Reviewed by Jer Noble. Entering element fullscreen resets the scroll view's content offset, which scrolls the page to the top and dispatches a scroll event once the deferred events are flushed. The offset is restored on exit, so the page is told it scrolled somewhere it was never meant to stay. Sites that virtualize a list on scroll position re-window it in response, unmounting rows away from the viewport. If the fullscreen element is in one of those rows it gets removed, which destroys a fullscreen iframe's frame and exits fullscreen. On bing.com/videos every video below the fold exits fullscreen right after entering. The fullscreen presentation does not depend on the offset, so leave it alone: make the saved offset optional so a default-constructed WKWebViewState no longer applies one, and stop explicitly resetting it on entry. Exiting still restores the offset saved by store(). * Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm: (WebKit::WKWebViewState::applyTo): (-[WKFullScreenWindowController _enterFullScreen:windowScene:completionHandler:]): Canonical link: https://commits.webkit.org/320084@main --- .../ios/fullscreen/WKFullScreenWindowControllerIOS.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm b/Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm index dd81d1a6af96..3507a1fba8ab 100644 --- a/Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm +++ b/Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm @@ -198,7 +198,7 @@ static void setLightspillEnabledForElementFullscreenLayer(CALayer *, bool) { } BOOL _savedContentInsetAdjustmentBehaviorWasExternallyOverridden = NO; #endif UIScrollViewContentInsetAdjustmentBehavior _savedContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic; - CGPoint _savedContentOffset = CGPointZero; + std::optional _savedContentOffset; BOOL _savedBouncesZoom = NO; BOOL _savedForceAlwaysUserScalable = NO; CGFloat _savedMinimumEffectiveDeviceWidth = baseMinimumEffectiveDeviceWidth; @@ -224,7 +224,8 @@ void applyTo(WKWebView* webView) else [scrollView _resetContentInset]; - scrollView.get().contentOffset = _savedContentOffset; + if (_savedContentOffset) + scrollView.get().contentOffset = *_savedContentOffset; scrollView.get().scrollIndicatorInsets = _savedScrollIndicatorInsets; #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) @@ -1210,7 +1211,6 @@ - (void)_enterFullScreen:(CGSize)mediaDimensions windowScene:(UIWindowScene *)wi [webView _setMinimumEffectiveDeviceWidth:0]; [webView _setViewScale:1.f]; [webView _setForcesInitialScaleFactor:YES]; - [webView _resetContentOffset]; [_window insertSubview:webView.get() atIndex:0]; WebKit::WKWebViewState().applyTo(webView.get()); [webView setNeedsLayout]; From 37082295192386f21f8e17f4e8295e18f61c1da3 Mon Sep 17 00:00:00 2001 From: Brian Weinstein Date: Fri, 28 Aug 2026 16:07:10 -0700 Subject: [PATCH 077/103] Enable the offscreen web extension API https://bugs.webkit.org/show_bug.cgi?id=322752 rdar://184269255 Reviewed by Kiara Rose. While we're here, fix an issue hit by the ios-safer-cpp bot and use dynamic_objc_cast. * Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml: * Source/WTF/wtf/PlatformEnableCocoa.h: * Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm: (WebKit::windowScene): Canonical link: https://commits.webkit.org/320085@main --- Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml | 8 ++++---- Source/WTF/wtf/PlatformEnableCocoa.h | 2 +- .../Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml index 679c0a30ed5c..fbe0592acd6d 100644 --- a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml +++ b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml @@ -10364,18 +10364,18 @@ WebExtensionBookmarksEnabled: WebExtensionOffscreenEnabled: type: bool - status: testable + status: stable category: extensions humanReadableName: "WebExtension Offscreen API" humanReadableDescription: "Enable support for WebExtensions using the Offscreen API" condition: ENABLE(WK_WEB_EXTENSIONS_OFFSCREEN) defaultValue: WebKitLegacy: - default: false + default: true WebKit: - default: false + default: true WebCore: - default: false + default: true WebExtensionSidebarEnabled: type: bool diff --git a/Source/WTF/wtf/PlatformEnableCocoa.h b/Source/WTF/wtf/PlatformEnableCocoa.h index 4d71a85c4cff..972895c156e8 100644 --- a/Source/WTF/wtf/PlatformEnableCocoa.h +++ b/Source/WTF/wtf/PlatformEnableCocoa.h @@ -1087,7 +1087,7 @@ #endif #if !defined(ENABLE_WK_WEB_EXTENSIONS_OFFSCREEN) -#define ENABLE_WK_WEB_EXTENSIONS_OFFSCREEN 0 && ENABLE_WK_WEB_EXTENSIONS +#define ENABLE_WK_WEB_EXTENSIONS_OFFSCREEN ENABLE_WK_WEB_EXTENSIONS #endif #if !defined(ENABLE_WK_WEB_EXTENSIONS_BOOKMARKS) diff --git a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm index 9aab3249ac33..3da62b429ce8 100644 --- a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm +++ b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIOffscreenCocoa.mm @@ -54,7 +54,7 @@ { for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { if ([scene isKindOfClass:UIWindowScene.class] && (scene.activationState == UISceneActivationStateForegroundActive || scene.activationState == UISceneActivationStateForegroundInactive)) - return (UIWindowScene *)scene; + return dynamic_objc_cast(scene); } return nil; } From 1c9b16dd56d5e2d6865ef612bf2323cb0c9c9920 Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Fri, 28 Aug 2026 17:04:51 -0700 Subject: [PATCH 078/103] [YARR] Fix lastIndex and ^ handling in dotAll mode https://bugs.webkit.org/show_bug.cgi?id=322037 rdar://185814135 Reviewed by Yusuke Suzuki. This PR fixes two bugs with dotAll mode (/s). One, lastIndex wasn't being respected in the JIT and was always considered zero. Two, ^.* was incorrectly handled in dotAll mode when the pattern was also in multiline mode (/m). In that case, we still optimized ^.* to a DotStarEnclosure, which would produce wrong results in multiline as ^ needs to check every line. DotStarEnclosure matches and tries to walk backwards and "expand" the .*. This optimization is wrong for the following example: const re = /^.*X.*/gms; re.lastIndex = 1; re.exec("aXb\ncXd"); "aXb\ncXd" has \n at index 3, so under m the ^ positions are 0 and 4. The search starts at 1, so 0 is out of reach and 4 is the only candidate. From 4, .*X.* matches "cXd". DotAllEnclosure is wrong in this case because it can't ever match something at position 4 by expanding leftward from the X at index 1. Test: JSTests/stress/regexp-dot-star-enclosure-dot-all-last-index.js * JSTests/stress/regexp-dot-star-enclosure-dot-all-last-index.js: Added. (shouldBe): (check): (step): * Source/JavaScriptCore/yarr/YarrInterpreter.cpp: (JSC::Yarr::Interpreter::matchDotStarEnclosure): * Source/JavaScriptCore/yarr/YarrJIT.cpp: * Source/JavaScriptCore/yarr/YarrPattern.cpp: (JSC::Yarr::YarrPatternConstructor::optimizeDotStarWrappedExpressions): Canonical link: https://commits.webkit.org/320086@main --- ...p-dot-star-enclosure-dot-all-last-index.js | 94 +++++++++++++++++++ .../JavaScriptCore/yarr/YarrInterpreter.cpp | 7 ++ Source/JavaScriptCore/yarr/YarrJIT.cpp | 12 ++- Source/JavaScriptCore/yarr/YarrPattern.cpp | 10 +- 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 JSTests/stress/regexp-dot-star-enclosure-dot-all-last-index.js diff --git a/JSTests/stress/regexp-dot-star-enclosure-dot-all-last-index.js b/JSTests/stress/regexp-dot-star-enclosure-dot-all-last-index.js new file mode 100644 index 000000000000..109882bf626f --- /dev/null +++ b/JSTests/stress/regexp-dot-star-enclosure-dot-all-last-index.js @@ -0,0 +1,94 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error("bad value: " + actual + " expected: " + expected); +} + +// Checks exec(), the lastIndex it leaves behind, and test(), which share the compiled path. +function check(re, lastIndex, string, expectedIndex, expectedMatch) { + re.lastIndex = lastIndex; + const result = re.exec(string); + if (expectedMatch === null) { + shouldBe(result, null); + shouldBe(re.lastIndex, 0); + } else { + shouldBe(result[0], expectedMatch); + shouldBe(result.index, expectedIndex); + shouldBe(re.lastIndex, expectedIndex + expectedMatch.length); + } + + re.lastIndex = lastIndex; + shouldBe(re.test(string), expectedMatch !== null); +} + +const dotAll = /.*X.*/gs; +const dotAllEOL = /.*X.*$/gs; +const dotAllMultiline = /.*X.*/gms; +// `s` is a no-op by construction here, since [\s\S] already matches every code point, so this must +// agree with the plain `g` spelling below. +const explicitAnyDotAll = new RegExp("[\\s\\S]*X[\\s\\S]*", "gs"); +const plain = /.*X.*/g; + +const bolDotAll = /^.*X.*/gs; +const bolEOLDotAll = /^.*X.*$/gs; +const bolDotAllMultiline = /^.*X.*/gms; +const bolEOLDotAllMultiline = /^.*X.*$/gms; +const bolPlain = /^.*X.*/g; +const bolPlainMultiline = /^.*X.*/gm; + +function step() { + // 1. A global match must begin at or after lastIndex. + check(dotAll, 0, "aaXb", 0, "aaXb"); + check(dotAll, 1, "aaXb", 1, "aXb"); + check(dotAll, 2, "aaXb", 2, "Xb"); + check(dotAll, 3, "aaXb", 0, null); + check(dotAll, 4, "aaXb", 0, null); + + check(dotAllEOL, 1, "aaXb", 1, "aXb"); + check(dotAll, 1, "aaXbXc", 1, "aXbXc"); + + // The enclosure still reaches across line terminators; it just cannot reach past lastIndex. + check(dotAll, 1, "aa\nXb", 1, "a\nXb"); + check(dotAllMultiline, 1, "aa\nXb", 1, "a\nXb"); + + check(explicitAnyDotAll, 1, "aaXb", 1, "aXb"); + check(plain, 1, "aaXb", 1, "aXb"); + + // matchAll() starts from lastIndex too, and its first yield was wrong for the same reason. + dotAll.lastIndex = 0; + const all = [...("aaXb".matchAll(dotAll))]; + shouldBe(all.length, 1); + shouldBe(all[0].index, 0); + shouldBe(all[0][0], "aaXb"); + + // 2. `^` still has to hold where the match is reported to begin. + check(bolDotAll, 0, "aaXb", 0, "aaXb"); + check(bolDotAll, 1, "aaXb", 0, null); + check(bolDotAll, 2, "aaXb", 0, null); + check(bolEOLDotAll, 0, "aaXb", 0, "aaXb"); + check(bolEOLDotAll, 1, "aaXb", 0, null); + + // Non-zero lastIndex is fine when `^` genuinely holds: under `m` it holds after a newline. + check(bolDotAllMultiline, 0, "aa\nXb", 0, "aa\nXb"); + check(bolDotAllMultiline, 1, "aa\nXb", 3, "Xb"); + check(bolDotAllMultiline, 3, "aa\nXb", 3, "Xb"); + check(bolDotAllMultiline, 1, "aaXb", 0, null); + check(bolEOLDotAllMultiline, 1, "aa\nXb", 3, "Xb"); + + // Under `m` the match can even have to begin at a line start *after* the X the enclosure would + // have matched: the leftmost X is at 1, but `^` does not hold there, so the match is "cXd" at 4. + // Widening backwards cannot reach that, which is why these patterns skip the enclosure. + check(bolDotAllMultiline, 1, "aXb\ncXd", 4, "cXd"); + check(bolDotAllMultiline, 2, "aXb\ncXd", 4, "cXd"); + check(bolDotAllMultiline, 4, "aXb\ncXd", 4, "cXd"); + check(bolDotAllMultiline, 5, "aXb\ncXd", 0, null); + check(bolEOLDotAllMultiline, 1, "aXb\ncXd", 4, "cXd"); + + // Non-dotAll spellings keep using the enclosure and must be unaffected. + check(bolPlain, 0, "aaXb", 0, "aaXb"); + check(bolPlain, 1, "aaXb", 0, null); + check(bolPlainMultiline, 0, "aa\nXb", 3, "Xb"); + check(bolPlainMultiline, 1, "aa\nXb", 3, "Xb"); +} + +for (var i = 0; i < testLoopCount; ++i) + step(); diff --git a/Source/JavaScriptCore/yarr/YarrInterpreter.cpp b/Source/JavaScriptCore/yarr/YarrInterpreter.cpp index 0ca0e668aead..2b64a1ae6567 100644 --- a/Source/JavaScriptCore/yarr/YarrInterpreter.cpp +++ b/Source/JavaScriptCore/yarr/YarrInterpreter.cpp @@ -1729,6 +1729,13 @@ class Interpreter { UNUSED_PARAM(term); if (term.dotAll()) { + // In dotAll mode, .* can match line terminators. A non-multiline ^ matches only if the + // search begins at the start of the input (offset 0). A multiline ^ needs to check + // every line and is never optimized to a DotStarEnclosure. + ASSERT(!(term.anchors.m_bol && term.multiline())); + if (startOffset && term.anchors.m_bol && !term.multiline()) + return false; + context->matchBegin = startOffset; context->matchEnd = input.end(); return true; diff --git a/Source/JavaScriptCore/yarr/YarrJIT.cpp b/Source/JavaScriptCore/yarr/YarrJIT.cpp index e0f82b899ab3..bed4fcab67aa 100644 --- a/Source/JavaScriptCore/yarr/YarrJIT.cpp +++ b/Source/JavaScriptCore/yarr/YarrJIT.cpp @@ -3744,7 +3744,17 @@ class YarrGenerator final : public YarrJITInfo { MacroAssembler::JumpList foundEndingNewLine; if (term->dotAll()) { - m_jit.move(MacroAssembler::TrustedImm32(0), matchPos); + ASSERT(m_pattern.m_saveInitialStartValue); + ASSERT(!m_pattern.m_body->m_hasFixedSize); + loadFromFrame(m_pattern.m_initialStartValueFrameLocation, matchPos); + + // In dotAll mode, .* can match line terminators. A non-multiline ^ matches only if the + // search begins at the start of the input (offset 0). A multiline ^ needs to check + // every line and is never optimized to a DotStarEnclosure. + ASSERT(!(term->anchors.bolAnchor && term->multiline())); + if (!term->multiline() && term->anchors.bolAnchor) + op.m_jumps.append(m_jit.branchTest32(MacroAssembler::NonZero, matchPos)); + setMatchStart(matchPos); m_jit.move(m_regs.length, m_regs.index); return; diff --git a/Source/JavaScriptCore/yarr/YarrPattern.cpp b/Source/JavaScriptCore/yarr/YarrPattern.cpp index c0c4df38df2c..7af73a4245b4 100644 --- a/Source/JavaScriptCore/yarr/YarrPattern.cpp +++ b/Source/JavaScriptCore/yarr/YarrPattern.cpp @@ -2599,7 +2599,15 @@ class YarrPatternConstructor { startsWithBOL = true; ++termIndex; } - + + // In dotAll mode, .* can match line terminators. In multiline mode, ^ matches the + // beginning of every line (instead of in non-multiline mode, it only matches the start + // of input). So a ^.* in the combined dotAll and multiline is not checkable by + // adjusting the beginning and the end of the match, because the match might begin at a + // line after the wrapped expression. In this case ^.* is not optimized. + if (startsWithBOL && dotAll() && multiline()) + return; + PatternTerm& firstNonAnchorTerm = terms[termIndex]; if (firstNonAnchorTerm.type != PatternTerm::Type::CharacterClass || firstNonAnchorTerm.characterClass != dotCharacterClass From 651c2a2cdcac1fb8bde41eb878db83fbf0525f31 Mon Sep 17 00:00:00 2001 From: Issac Roy Date: Fri, 28 Aug 2026 17:19:17 -0700 Subject: [PATCH 079/103] [EWS] Activate flaky test verdicts https://bugs.webkit.org/show_bug.cgi?id=322866 rdar://186115028 Reviewed by Aakash Jain. EWS computes a flakiness verdict for every layout-test failure it cannot explain as a pre-existing failure, then throws it away: `SHOULD_IGNORE_FLAKY_TESTS` is `False`, so a convicted test stays in `failing_tests_filtered` and still blames the pull request. A recent 48-hour window produced 1292 such convictions across 4737 builds. Act on them. `INCLUDED_FLAKY_VERDICTS` names the verdicts a queue ignores a failure for, one entry per verdict rather than one boolean for all three, so a verdict can be withdrawn from production without reverting the read path. All three are included. The results-db log reports `Ignored` and `Would have ignored` separately and counts the tests each applied to, not every conviction. `AUTHORS_FOR_DIRTY_TREE_FLAKE` rises from 1 to 2. A build's first run reports its own flakes before its re-run reads them back, so a `DirtyTree` conviction could rest on one author's stack of pull requests excusing its own regression. Requiring a second author makes the evidence independent of the change under test. * Tools/CISupport/ews-build/results_db.py: (ResultsDatabase): (ResultsDatabase._is_intra_build_flake): (ResultsDatabase._is_inter_build_flake): * Tools/CISupport/ews-build/steps.py: (RunWebKitTests): (RunWebKitTests.filter_failures_using_results_db): * Tools/CISupport/ews-build/steps_unittest.py: (TestFilterLayoutTestFailuresUsingResultsDB.test_a_verdict_in_the_included_set_removes_the_failure): (TestFilterLayoutTestFailuresUsingResultsDB): (TestFilterLayoutTestFailuresUsingResultsDB.test_a_verdict_outside_the_included_set_is_recorded_without_ignoring_the_failure): (TestFilterLayoutTestFailuresUsingResultsDB.test_the_ignore_message_covers_both_categories): (TestFilterLayoutTestFailuresUsingResultsDB.test_a_flaky_verdict_is_recorded_without_ignoring_the_failure): Deleted. Canonical link: https://commits.webkit.org/320087@main --- Tools/CISupport/ews-build/results_db.py | 13 +++++--- Tools/CISupport/ews-build/steps.py | 27 +++++++++------ Tools/CISupport/ews-build/steps_unittest.py | 37 ++++++++++++++++++--- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/Tools/CISupport/ews-build/results_db.py b/Tools/CISupport/ews-build/results_db.py index 09dde1de7cfa..cbf1a284d0d3 100755 --- a/Tools/CISupport/ews-build/results_db.py +++ b/Tools/CISupport/ews-build/results_db.py @@ -86,7 +86,7 @@ class ResultsDatabase(object): ] PRS_FOR_DIRTY_TREE_FLAKE = 2 - AUTHORS_FOR_DIRTY_TREE_FLAKE = 1 + AUTHORS_FOR_DIRTY_TREE_FLAKE = 2 PRS_FOR_BETWEEN_BUILD_FLAKE = 3 AUTHORS_FOR_BETWEEN_BUILD_FLAKE = 2 @@ -100,6 +100,11 @@ class ResultsDatabase(object): WITHIN_STEP_DIRTY_TREE = 'WithinStepDirtyTree' BETWEEN_STEPS_DIRTY_TREE = 'BetweenStepsDirtyTree' + # What the read path concludes, which is what a caller decides to act on. + CLEAN_TREE_VERDICT = 'CleanTree' + DIRTY_TREE_VERDICT = 'DirtyTree' + BETWEEN_BUILDS_VERDICT = 'BetweenBuilds' + @classmethod def platform_for_query(cls, platform): if platform.lower() in ('gtk', 'wpe'): @@ -313,12 +318,12 @@ def _is_intra_build_flake(cls, entries, logger): if clean_tree := rows.get(cls.WITHIN_STEP_CLEAN_TREE): evidence = cls._evidence_in(clean_tree) - evidence.flaky_type = 'CleanTree' + evidence.flaky_type = cls.CLEAN_TREE_VERDICT return evidence with_change = rows.get(cls.WITHIN_STEP_DIRTY_TREE, []) + rows.get(cls.BETWEEN_STEPS_DIRTY_TREE, []) return cls._convict( - cls._evidence_in(with_change), 'DirtyTree', + cls._evidence_in(with_change), cls.DIRTY_TREE_VERDICT, cls.PRS_FOR_DIRTY_TREE_FLAKE, cls.AUTHORS_FOR_DIRTY_TREE_FLAKE, ) @@ -328,7 +333,7 @@ def _is_inter_build_flake(cls, entries, logger): evidence = cls._evidence_in(rows) if verdict := cls._convict( - evidence, 'BetweenBuilds', + evidence, cls.BETWEEN_BUILDS_VERDICT, cls.PRS_FOR_BETWEEN_BUILD_FLAKE, cls.AUTHORS_FOR_BETWEEN_BUILD_FLAKE, ): return verdict diff --git a/Tools/CISupport/ews-build/steps.py b/Tools/CISupport/ews-build/steps.py index ac6dac226db9..1cd421cf4413 100644 --- a/Tools/CISupport/ews-build/steps.py +++ b/Tools/CISupport/ews-build/steps.py @@ -3949,7 +3949,11 @@ class RunWebKitTests(shell.Test, ResultsDBReportMixin, AddToLogMixin, ShellMixin ENABLE_ADDITIONAL_ARGUMENTS = True EXIT_AFTER_FAILURES = '60' MAX_FAILURES_TO_CHECK_RESULTS_DB = 60 - SHOULD_IGNORE_FLAKY_TESTS = False + INCLUDED_FLAKY_VERDICTS = frozenset({ + ResultsDatabase.CLEAN_TREE_VERDICT, + ResultsDatabase.DIRTY_TREE_VERDICT, + ResultsDatabase.BETWEEN_BUILDS_VERDICT, + }) STRESS_MODE = False command = ['python3', 'Tools/Scripts/run-webkit-tests', '--no-build', @@ -4125,6 +4129,7 @@ def filter_failures_using_results_db(self, failing_tests): if flake_logs: yield self._addToLog(self.results_db_log_name, flake_logs) + ignored, shadowed = [], [] for test in tests: data = pre_existing[test] flake = flakes.get(test, FlakyVerdict(request_failed=True)) @@ -4134,11 +4139,14 @@ def filter_failures_using_results_db(self, failing_tests): self.failing_tests_filtered.remove(test) elif flake.is_flaky: self.flaky_failures_in_results_db[test] = flake.flaky_type - if flake.flaky_type == 'BetweenBuilds' and not flake.intra_build_evidence: + if flake.flaky_type == ResultsDatabase.BETWEEN_BUILDS_VERDICT and not flake.intra_build_evidence: self.unsupported_flakes_in_results_db.append(test) flake_summary = f'{flake.flaky_type}: {flake.evidence}' - if self.SHOULD_IGNORE_FLAKY_TESTS: + if flake.flaky_type in self.INCLUDED_FLAKY_VERDICTS: + ignored.append(test) self.failing_tests_filtered.remove(test) + else: + shadowed.append(test) elif flake.request_failed: self.unknown_flakes_in_results_db.append(test) flake_summary = 'Unknown' @@ -4151,13 +4159,12 @@ def filter_failures_using_results_db(self, failing_tests): f"pre-existing-flake={flake_summary}\n" ) - if self.flaky_failures_in_results_db: - action = 'Ignored' if self.SHOULD_IGNORE_FLAKY_TESTS else 'Would have ignored' - yield self._addToLog( - self.results_db_log_name, - f"\n{action} {len(self.flaky_failures_in_results_db)} flaky " - f"test(s): {', '.join(sorted(self.flaky_failures_in_results_db))}\n", - ) + for action, acted_on in (('Ignored', ignored), ('Would have ignored', shadowed)): + if acted_on: + yield self._addToLog( + self.results_db_log_name, + f"\n{action} {len(acted_on)} flaky test(s): {', '.join(sorted(acted_on))}\n", + ) def results_db_ignore_message(self) -> str: parts = [] diff --git a/Tools/CISupport/ews-build/steps_unittest.py b/Tools/CISupport/ews-build/steps_unittest.py index 6b9be0b936e2..ed6924dd8f4e 100644 --- a/Tools/CISupport/ews-build/steps_unittest.py +++ b/Tools/CISupport/ews-build/steps_unittest.py @@ -30,6 +30,7 @@ import sys import tempfile import time +from typing import Any, Generator from unittest import skip as skipTest from unittest.mock import call, create_autospec, patch @@ -3359,8 +3360,7 @@ def test_pre_existing_interleaved_with_real_failure_stripped(self): ) @defer.inlineCallbacks - def test_a_flaky_verdict_is_recorded_without_ignoring_the_failure(self): - # SHOULD_IGNORE_FLAKY_TESTS is False, so the verdict is recorded but the test is not removed. + def test_a_verdict_in_the_included_set_removes_the_failure(self) -> Generator[Any, Any, None]: step = self._configure(set()) builds = [f'https://build.webkit.org/#/builders/1/builds/{number}' for number in (11, 12, 13)] self.patch(ResultsDatabase, 'flaky_verdicts_for', classmethod( @@ -3373,11 +3373,28 @@ def test_a_flaky_verdict_is_recorded_without_ignoring_the_failure(self): yield step.filter_failures_using_results_db(['real.html', 'flaky.html']) - self.assertEqual(step.failing_tests_filtered, ['real.html', 'flaky.html']) + self.assertEqual(step.failing_tests_filtered, ['real.html']) self.assertEqual(step.flaky_failures_in_results_db, {'flaky.html': 'DirtyTree'}) self.assertEqual(step.preexisting_failures_in_results_db, []) self.assertEqual(step.results_db_ignore_message(), 'Ignored flaky tests: flaky.html based on results-db') + @defer.inlineCallbacks + def test_a_verdict_outside_the_included_set_is_recorded_without_ignoring_the_failure(self) -> Generator[Any, Any, None]: + step = self._configure(set()) + step.INCLUDED_FLAKY_VERDICTS = frozenset({'CleanTree'}) + self.patch(ResultsDatabase, 'flaky_verdicts_for', classmethod( + lambda cls, tests, **kwargs: defer.succeed(({ + test: ( + FlakyVerdict(flaky_type='BetweenBuilds') + if test == 'flaky.html' else FlakyVerdict() + ) for test in tests + }, '')))) + + yield step.filter_failures_using_results_db(['real.html', 'flaky.html']) + + self.assertEqual(step.failing_tests_filtered, ['real.html', 'flaky.html']) + self.assertEqual(step.flaky_failures_in_results_db, {'flaky.html': 'BetweenBuilds'}) + @defer.inlineCallbacks def test_a_test_with_no_verdict_is_not_treated_as_sound(self): # flaky_verdicts_for answers for every test it is given, so this cannot happen today. If that @@ -3404,7 +3421,7 @@ def test_the_ignore_message_covers_both_categories(self): yield step.filter_failures_using_results_db(['pre-existing.html', 'flaky.html']) - self.assertEqual(step.failing_tests_filtered, ['flaky.html']) + self.assertEqual(step.failing_tests_filtered, []) self.assertEqual( step.results_db_ignore_message(), 'Ignored pre-existing failures: pre-existing.html; flaky tests: flaky.html based on results-db', @@ -12590,6 +12607,18 @@ def test_a_missing_pull_request_is_not_a_second_pull_request(self): verdicts, _ = yield ResultsDatabase.flaky_verdicts_for(['layout/test.html'], suite='layout-tests') self.assertFalse(verdicts['layout/test.html'].is_flaky) + @defer.inlineCallbacks + def test_a_single_author_across_two_pull_requests_is_not_flaky(self) -> Generator[Any, Any, None]: + # A stack of pull requests is one author's work, so the parent commit's own regression must + # not excuse itself: a build's first run writes rows its own re-run then reads. + rows = [ + self._flaky_row('WithinStepDirtyTree', build_url=f'https://build/1/builds/{n}', pr_number=n, authors=['alice']) + for n in range(1, 4) + ] + with self._mock(self._response(rows=rows), self._response()): + verdicts, _ = yield ResultsDatabase.flaky_verdicts_for(['layout/test.html'], suite='layout-tests') + self.assertFalse(verdicts['layout/test.html'].is_flaky) + @defer.inlineCallbacks def test_dirty_tree_pull_requests_with_no_author_not_flaky(self): rows = [ From 24a47dd197933b56dd372a9c4d181515a2c60354 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Fri, 28 Aug 2026 17:22:55 -0700 Subject: [PATCH 080/103] [css-overflow-4] `text-overflow: ` should work on `` https://bugs.webkit.org/show_bug.cgi?id=322843 rdar://186085377 Reviewed by Alan Baradlay. Stop checking explicitly for ellipsis text-overflow, and instead check for non-clip text-overflow. Transfer non-clip text-overflow value into the inner text element. Tests: imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-notref.html imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input.html * LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-expected-mismatch.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-notref.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input.html: Added. * Source/WebCore/html/HTMLInputElement.cpp: (WebCore::HTMLInputElement::shouldTruncateText const): (WebCore::HTMLInputElement::invalidateStyleOnFocusChangeIfNeeded): (WebCore::HTMLInputElement::createInnerTextStyle): Canonical link: https://commits.webkit.org/320088@main --- .../text-overflow-string-in-input-expected-mismatch.html | 4 ++++ .../css-overflow/text-overflow-string-in-input-notref.html | 4 ++++ .../css/css-overflow/text-overflow-string-in-input.html | 7 +++++++ Source/WebCore/html/HTMLInputElement.cpp | 6 +++--- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-expected-mismatch.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-notref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-expected-mismatch.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-expected-mismatch.html new file mode 100644 index 000000000000..be13855d5345 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-expected-mismatch.html @@ -0,0 +1,4 @@ + + +text-overflow: <string> in text inputs - not reference + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-notref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-notref.html new file mode 100644 index 000000000000..be13855d5345 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input-notref.html @@ -0,0 +1,4 @@ + + +text-overflow: <string> in text inputs - not reference + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input.html new file mode 100644 index 000000000000..c37ce537af5e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-in-input.html @@ -0,0 +1,7 @@ + + +text-overflow: <string> in text inputs + + + + diff --git a/Source/WebCore/html/HTMLInputElement.cpp b/Source/WebCore/html/HTMLInputElement.cpp index d8f93e7469c8..abfdfb504aed 100644 --- a/Source/WebCore/html/HTMLInputElement.cpp +++ b/Source/WebCore/html/HTMLInputElement.cpp @@ -2269,7 +2269,7 @@ bool HTMLInputElement::shouldTruncateText(const Style::ComputedStyle& style) con { if (!isTextField()) return false; - return document().focusedElement() != this && style.textOverflow().isEllipsis(); + return document().focusedElement() != this && !style.textOverflow().isClip(); } void HTMLInputElement::invalidateStyleOnFocusChangeIfNeeded() @@ -2277,7 +2277,7 @@ void HTMLInputElement::invalidateStyleOnFocusChangeIfNeeded() if (!isTextField()) return; // Focus change may affect the result of shouldTruncateText(). - if (CheckedPtr style = renderStyle(); style && style->textOverflow().isEllipsis()) + if (CheckedPtr style = renderStyle(); style && !style->textOverflow().isClip()) invalidateStyleForSubtree(); } @@ -2369,7 +2369,7 @@ Style::ComputedStyle HTMLInputElement::createInnerTextStyle(const Style::Compute textBlockStyle.setOverflowX(Overflow::Hidden); textBlockStyle.setOverflowY(Overflow::Hidden); if (shouldTruncateText(style)) - textBlockStyle.setTextOverflow(CSS::Keyword::Ellipsis { }); + textBlockStyle.setTextOverflow(Style::TextOverflow { style.textOverflow() }); else textBlockStyle.setTextOverflow(CSS::Keyword::Clip { }); From f54ee018c7d9f6f165f099f9da968050d4aae32f Mon Sep 17 00:00:00 2001 From: Yusuke Suzuki Date: Fri, 28 Aug 2026 17:37:08 -0700 Subject: [PATCH 081/103] Unreviewed, fix JSCOnly build https://bugs.webkit.org/show_bug.cgi?id=322877 rdar://186123945 * Source/WTF/wtf/linux/HighPriorityThreads.cpp: Canonical link: https://commits.webkit.org/320089@main --- Source/WTF/wtf/linux/HighPriorityThreads.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/WTF/wtf/linux/HighPriorityThreads.cpp b/Source/WTF/wtf/linux/HighPriorityThreads.cpp index fecd6728781a..d529d2f0d2c2 100644 --- a/Source/WTF/wtf/linux/HighPriorityThreads.cpp +++ b/Source/WTF/wtf/linux/HighPriorityThreads.cpp @@ -42,7 +42,7 @@ namespace WTF { // Requested nice value. rtkit clamps this to its own MinNiceLevel. -static constexpr int s_highPriorityNiceLevel = -20; +[[maybe_unused]] static constexpr int s_highPriorityNiceLevel = -20; HighPriorityThreads& HighPriorityThreads::singleton() { From 31fe3281c1d7aa94986d9da4a899729d3a457554 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Fri, 28 Aug 2026 18:08:43 -0700 Subject: [PATCH 082/103] Re-import css/css-overflow & css/css-ui WPT https://bugs.webkit.org/show_bug.cgi?id=322846 rdar://186085908 Reviewed by Sammy Gill. Upstream commit: https://github.com/web-platform-tests/wpt/commit/f01d70241ee0aebcdd2fd68ea4e9e2ff309d78ce These directories were imported together because some text-overflow tests were moved from css-ui to css-overflow. * LayoutTests/imported/w3c/web-platform-tests/css/css-overflow * LayoutTests/imported/w3c/web-platform-tests/css/css-ui Canonical link: https://commits.webkit.org/320090@main --- LayoutTests/TestExpectations | 97 +++++-- .../w3c/resources/resource-files.json | 122 +++++--- .../css/css-overflow/WEB_FEATURES.yml | 51 ++-- ...ter-pseudo-element-scrolling-expected.html | 6 + ...before-after-pseudo-element-scrolling.html | 59 ++++ .../css-overflow/chrome-480554290-crash.html | 30 ++ .../css/css-overflow/clip-002.html | 1 - .../css/css-overflow/clip-004.html | 1 - .../css/css-overflow/clip-005.html | 1 - .../crashtests/WEB_FEATURES.yml | 0 ...ext-overflow-ellipsis-multiline-crash.html | 0 .../css-overflow/crashtests/w3c-import.log | 16 ++ .../dynamic-visible-to-clip-002-expected.html | 12 + .../dynamic-visible-to-clip-002-ref.html | 12 + .../dynamic-visible-to-clip-002.html | 24 ++ ...us-and-perspective-projection-expected.txt | 4 + ...der-radius-and-perspective-projection.html | 63 ++++ ...-context-parent-border-radius-expected.txt | 45 ++- ...stacking-context-parent-border-radius.html | 45 ++- .../css/css-overflow/inheritance-expected.txt | 6 +- .../css/css-overflow/inheritance.html | 6 +- .../css-overflow/line-clamp/WEB_FEATURES.yml | 5 +- ....html => block-ellipsis-032-expected.html} | 0 ...tentative.html => block-ellipsis-032.html} | 0 .../block-ellipsis-033-expected.xht} | 0 .../line-clamp/block-ellipsis-033.html | 56 ++++ .../block-ellipsis-034-expected.html | 11 + .../line-clamp/block-ellipsis-034.html | 27 ++ .../block-ellipsis-035-expected.html | 14 + .../line-clamp/block-ellipsis-035.html | 25 ++ .../block-ellipsis-036-expected.xht} | 0 .../line-clamp/block-ellipsis-036.html | 38 +++ .../block-ellipsis-037-expected.html | 16 ++ .../line-clamp/block-ellipsis-037.html | 22 ++ .../block-ellipsis-038-expected.html | 10 + .../line-clamp/block-ellipsis-038.html | 14 + .../block-ellipsis-039-expected.html | 27 ++ .../line-clamp/block-ellipsis-039.html | 33 +++ .../block-ellipsis-040-expected.xht} | 0 .../line-clamp/block-ellipsis-040.html | 37 +++ .../block-ellipsis-041-expected.html | 22 ++ .../line-clamp/block-ellipsis-041.html | 28 ++ .../block-ellipsis-bidi-001-expected.html | 16 ++ .../line-clamp/block-ellipsis-bidi-001.html | 21 ++ .../block-ellipsis-bidi-002-expected.html | 20 ++ .../line-clamp/block-ellipsis-bidi-002.html | 25 ++ .../block-ellipsis-bidi-003-expected.html | 28 ++ .../line-clamp/block-ellipsis-bidi-003.html | 35 +++ .../block-ellipsis-bidi-004-expected.html | 22 ++ .../line-clamp/block-ellipsis-bidi-004.html | 28 ++ .../line-clamp/block-ellipsis-crash-001.html | 15 + .../block-ellipsis-quirk-001-expected.xht} | 0 .../line-clamp/block-ellipsis-quirk-001.html | 50 ++++ .../block-ellipsis-quirk-002-expected.xht} | 0 .../line-clamp/block-ellipsis-quirk-002.html | 44 +++ .../discard/reference/w3c-import.log | 2 - .../line-clamp/discard/w3c-import.log | 2 - .../line-clamp/line-clamp-011-expected.html | 10 +- .../line-clamp/line-clamp-011.html | 3 +- .../line-clamp/line-clamp-026-expected.html | 1 + .../line-clamp/line-clamp-035-expected.html | 11 +- .../line-clamp/line-clamp-035.html | 16 +- .../line-clamp/line-clamp-039-expected.html | 26 ++ .../line-clamp/line-clamp-039.html | 32 +++ .../line-clamp/line-clamp-040-crash.html | 13 + .../line-clamp/line-clamp-041-expected.html | 15 + .../line-clamp/line-clamp-041.html | 23 ++ ...cted.html => line-clamp-042-expected.html} | 0 ...009.tentative.html => line-clamp-042.html} | 16 +- ...html => line-clamp-auto-042-expected.html} | 0 .../line-clamp/line-clamp-auto-042.html | 34 +++ .../line-clamp-auto-043-expected.html | 22 ++ .../line-clamp/line-clamp-auto-043.html | 30 ++ .../line-clamp-auto-044-expected.html | 20 ++ .../line-clamp/line-clamp-auto-044.html | 31 ++ .../line-clamp-auto-045-expected.html | 41 +++ .../line-clamp/line-clamp-auto-045.html | 49 ++++ .../line-clamp-auto-046-expected.html | 29 ++ .../line-clamp/line-clamp-auto-046.html | 35 +++ .../line-clamp-auto-047-expected.html | 28 ++ .../line-clamp/line-clamp-auto-047.html | 42 +++ .../line-clamp/line-clamp-bfc-expected.html | 28 ++ .../line-clamp/line-clamp-bfc-ref.html | 28 ++ .../line-clamp/line-clamp-bfc.html | 28 ++ ... line-clamp-with-floats-001-expected.html} | 0 ...e.html => line-clamp-with-floats-001.html} | 0 ... line-clamp-with-floats-002-expected.html} | 0 ...e.html => line-clamp-with-floats-002.html} | 0 ... line-clamp-with-floats-003-expected.html} | 0 ...e.html => line-clamp-with-floats-003.html} | 0 ... line-clamp-with-floats-004-expected.html} | 14 +- ...e.html => line-clamp-with-floats-004.html} | 0 ... line-clamp-with-floats-005-expected.html} | 11 +- ...e.html => line-clamp-with-floats-005.html} | 4 +- ... line-clamp-with-floats-006-expected.html} | 13 +- ...e.html => line-clamp-with-floats-006.html} | 6 +- ...mp-with-floats-006.tentative-expected.html | 28 -- ... line-clamp-with-floats-007-expected.html} | 17 +- ...e.html => line-clamp-with-floats-007.html} | 7 +- .../line-clamp-with-floats-008.html | 31 -- ... line-clamp-with-floats-010-expected.html} | 17 +- ...e.html => line-clamp-with-floats-010.html} | 6 +- .../line-clamp-with-floats-011-expected.xht} | 0 .../line-clamp-with-floats-011.html | 38 +++ .../line-clamp-with-floats-012-expected.xht} | 0 .../line-clamp-with-floats-012.html | 58 ++++ .../reference/block-ellipsis-034-ref.html | 11 + .../reference/block-ellipsis-035-ref.html | 14 + .../reference/block-ellipsis-038-ref.html | 10 + .../reference/block-ellipsis-039-ref.html | 27 ++ .../reference/block-ellipsis-041-ref.html | 22 ++ .../block-ellipsis-bidi-002-ref.html | 20 ++ .../block-ellipsis-bidi-003-ref.html | 28 ++ .../block-ellipsis-bidi-004-ref.html | 22 ++ .../reference/line-clamp-011-ref.html | 10 +- .../reference/line-clamp-026-ref.html | 1 + .../reference/line-clamp-039-ref.html | 26 ++ .../reference/line-clamp-041-ref.html | 15 + .../reference/line-clamp-auto-043-ref.html | 22 ++ .../reference/line-clamp-auto-044-ref.html | 20 ++ .../reference/line-clamp-auto-045-ref.html | 41 +++ .../reference/line-clamp-auto-046-ref.html | 29 ++ .../reference/line-clamp-auto-047-ref.html | 28 ++ .../line-clamp-with-floats-005-ref.html | 11 +- .../line-clamp-with-floats-006-ref.html | 28 -- .../line-clamp-with-floats-007-ref.html | 17 +- .../line-clamp-with-floats-010-ref.html | 17 +- .../line-clamp/reference/w3c-import.log | 19 +- .../css-overflow/line-clamp/w3c-import.log | 106 +++++-- .../webkit-line-clamp-044-expected.html | 25 -- .../line-clamp/webkit-line-clamp-044.html | 35 --- .../webkit-line-clamp-047-expected.html | 25 -- .../line-clamp/webkit-line-clamp-047.html | 38 --- ...t-line-clamp-with-max-height-expected.html | 11 +- .../webkit-line-clamp-with-max-height.html | 17 +- ...-container-with-scrollable-descendant.html | 2 +- ...es-scroll-offsets-vertical-rl-expected.txt | 3 + ...nd-ignores-scroll-offsets-vertical-rl.html | 79 +++++ ...clip-content-visual-overflow-expected.html | 2 +- ...flow-clip-content-visual-overflow-ref.html | 2 +- ...overflow-clip-content-visual-overflow.html | 12 +- .../overflow-clip-hit-testing.html | 2 +- .../overflow-clip-margin-003-expected.html | 4 +- .../overflow-clip-margin-003-ref.html | 4 +- .../overflow-clip-margin-003.html | 14 +- .../overflow-clip-margin-006-expected.html | 4 +- .../overflow-clip-margin-006-ref.html | 4 +- .../overflow-clip-margin-006.html | 14 +- .../overflow-clip-margin-013-expected.html | 4 + .../overflow-clip-margin-013.html | 31 ++ .../overflow-clip-margin-014-expected.html | 4 + .../overflow-clip-margin-014.html | 32 +++ .../overflow-clip-margin-015-expected.html | 4 + .../overflow-clip-margin-015.html | 31 ++ .../overflow-clip-margin-016-expected.html | 4 + .../overflow-clip-margin-016.html | 33 +++ .../overflow-clip-margin-017-expected.html | 4 + .../overflow-clip-margin-017.html | 33 +++ .../overflow-clip-margin-018-expected.html | 4 + .../overflow-clip-margin-018.html | 32 +++ .../overflow-clip-margin-019-expected.html | 4 + .../overflow-clip-margin-019.html | 33 +++ .../overflow-clip-margin-020-expected.html | 4 + .../overflow-clip-margin-020.html | 33 +++ .../overflow-clip-margin-021-expected.html | 4 + .../overflow-clip-margin-021.html | 34 +++ .../overflow-clip-margin-022-expected.html | 4 + .../overflow-clip-margin-022.html | 34 +++ ...p-margin-content-box-dynamic-expected.html | 4 + ...rflow-clip-margin-content-box-dynamic.html | 32 +++ ...ow-clip-no-off-axis-scrollbar-expected.txt | 3 + .../overflow-clip-no-off-axis-scrollbar.html | 42 +++ ...pped-transparent-border-clip-expected.html | 35 +++ ...w-clipped-transparent-border-clip-ref.html | 35 +++ ...rflow-clipped-transparent-border-clip.html | 56 ++++ ...en-resize-with-stacking-context-child.html | 9 + ...erflow-video-hidden-expected-mismatch.html | 21 ++ .../css-overflow/overflow-video-hidden.html | 29 ++ .../css/css-overflow/parsing/WEB_FEATURES.yml | 29 +- .../block-ellipsis-invalid-expected.txt | 3 + .../parsing/block-ellipsis-invalid.html | 5 +- .../parsing/block-ellipsis-valid-expected.txt | 2 +- .../parsing/block-ellipsis-valid.html | 4 +- .../parsing/continue-invalid-expected.txt | 5 + .../parsing/continue-invalid.html | 7 +- .../parsing/continue-valid-expected.txt | 2 +- .../css-overflow/parsing/continue-valid.html | 4 +- ...etComputedStyle-scroll-button-expected.txt | 7 + .../getComputedStyle-scroll-button.html | 143 ++++++++++ .../parsing/line-clamp-invalid-expected.txt | 1 + .../parsing/line-clamp-invalid.html | 1 + .../parsing/line-clamp-valid-expected.txt | 7 +- .../parsing/line-clamp-valid.html | 16 +- .../parsing/max-lines-invalid-expected.txt | 3 +- .../parsing/max-lines-invalid.html | 3 +- .../parsing/max-lines-valid-expected.txt | 4 +- .../css-overflow/parsing/max-lines-valid.html | 4 +- .../parsing/overflow-clip-margin-expected.txt | 1 + .../parsing/overflow-clip-margin.html | 1 + .../parsing/overflow-computed-expected.txt | 12 +- .../parsing/overflow-computed.html | 16 +- .../scroll-axis-lock-computed-expected.txt | 10 + .../parsing/scroll-axis-lock-computed.html | 27 ++ .../scroll-axis-lock-invalid-expected.txt | 9 + .../parsing/scroll-axis-lock-invalid.html | 17 ++ .../scroll-axis-lock-valid-expected.txt | 8 + .../parsing/scroll-axis-lock-valid.html | 17 ++ .../parsing/scroll-markers-computed.html | 4 +- .../css/css-overflow/parsing/w3c-import.log | 6 +- ...re-after-pseudo-element-scrolling-ref.html | 6 + .../reference/text-overflow-001-ref.html | 0 .../reference/text-overflow-002-ref.html | 0 .../reference/text-overflow-005-ref.html | 0 .../reference/text-overflow-006-ref.html | 0 .../reference/text-overflow-008-ref.html | 0 .../reference/text-overflow-012-ref.html | 2 +- .../reference/text-overflow-013-ref.html | 0 .../reference/text-overflow-016-ref.html | 0 .../reference/text-overflow-021-ref.html | 0 .../reference/text-overflow-022-ref.html | 2 +- .../reference/text-overflow-027-ref.html | 0 .../reference/text-overflow-028-ref.html | 0 .../reference/text-overflow-029-ref.html | 0 .../reference/text-overflow-030-ref.html | 0 .../text-overflow-change-color-ref.html | 0 ...-ellipsis-editable-div-with-caret-ref.html | 29 ++ ...text-overflow-ellipsis-indent-001-ref.html | 0 ...t-overflow-ellipsis-multiline-001-ref.html | 0 ...flow-ellipsis-textarea-with-caret-ref.html | 25 ++ .../text-overflow-string-001-ref.html | 0 .../text-overflow-string-002-ref.html | 0 .../text-overflow-string-003-ref.html | 0 .../text-overflow-string-004-ref.html | 0 .../text-overflow-string-005-ref.html | 0 .../text-overflow-string-006-ref.html | 0 .../text-overflow-string-007-ref.html | 0 .../text-overflow-string-008-ref.html | 0 .../text-overflow-string-009-ref.html | 25 ++ .../text-overflow-string-010-ref.html | 25 ++ .../text-overflow-string-011-ref.html | 21 ++ .../text-overflow-string-012-ref.html | 29 ++ .../text-overflow-string-013-ref.html | 25 ++ .../text-overflow-string-014-ref.html | 25 ++ .../text-overflow-string-015-ref.html | 29 ++ .../text-overflow-string-016-ref.html | 21 ++ ...ext-overflow-string-024-ref.tentative.html | 21 ++ ...ext-overflow-string-025-ref.tentative.html | 21 ++ ...ext-overflow-string-026-ref.tentative.html | 21 ++ .../css/css-overflow/reference/w3c-import.log | 41 ++- .../scroll-axis-lock-expected.txt | 6 + .../css/css-overflow/scroll-axis-lock.html | 125 ++++++++ ...oll-marker-group-display-none-expected.txt | 4 + .../scroll-marker-group-display-none.html | 47 +++ .../scroll-marker-group-hover-expected.txt | 3 + ...arker-group-hover-from-marker-expected.txt | 3 + ...scroll-marker-group-hover-from-marker.html | 64 +++++ .../scroll-marker-group-hover.html | 42 +++ .../scroll-markers/WEB_FEATURES.yaml | 18 -- .../scroll-markers/WEB_FEATURES.yml | 15 + .../scroll-markers/resources/w3c-import.log | 2 - .../scroll-button-display-none.html | 18 ++ ...button-reattachment-position-expected.html | 15 + ...roll-button-reattachment-position-ref.html | 15 + .../scroll-button-reattachment-position.html | 34 +++ ...croll-marker-activation-retains-focus.html | 21 +- .../scroll-marker-double-activation.html | 31 +- .../scroll-marker-focus-within.html | 29 +- .../scroll-marker-inert-003.html | 49 ++++ .../scroll-marker-navigation-cycles.html | 188 ++++++++++-- ...scroll-marker-selection-picks-closest.html | 2 +- .../scroll-markers-focus-active-element.html | 21 +- .../scroll-markers-focus-on-scrolling.html | 30 +- .../scroll-markers-nested-scrollers.html | 87 ++++++ .../scroll-target-group-014.html | 76 +++++ .../scroll-target-group-iframe.html | 81 ++++++ .../scroll-markers/support/w3c-import.log | 2 - ...-scroll-marker-selection-001-expected.html | 7 +- ...olumn-scroll-marker-selection-001-ref.html | 7 +- ...ed-column-scroll-marker-selection-001.html | 1 - ...-scroll-marker-selection-002-expected.html | 5 +- ...olumn-scroll-marker-selection-002-ref.html | 5 +- ...ed-column-scroll-marker-selection-002.html | 1 - ...-scroll-marker-selection-003-expected.html | 9 +- ...olumn-scroll-marker-selection-003-ref.html | 9 +- ...ed-column-scroll-marker-selection-003.html | 5 +- ...-scroll-marker-selection-004-expected.html | 6 +- ...olumn-scroll-marker-selection-004-ref.html | 6 +- ...ed-column-scroll-marker-selection-004.html | 1 - .../scroll-markers/w3c-import.log | 11 +- .../scrollbar-gutter-zero-width-crash.html | 16 ++ ...ingle-axis-overflow-clip-rtl-expected.html | 25 ++ .../single-axis-overflow-clip-rtl-ref.html | 25 ++ .../single-axis-overflow-clip-rtl.html | 25 ++ ...axis-overflow-scroll-to-clip-expected.html | 43 +++ ...ngle-axis-overflow-scroll-to-clip-ref.html | 43 +++ .../single-axis-overflow-scroll-to-clip.html | 91 ++++++ ...ngle-axis-scroll-apis-dynamic-expected.txt | 3 + .../single-axis-scroll-apis-dynamic.html | 51 ++++ ...axis-scroll-apis-programmatic-expected.txt | 6 + .../single-axis-scroll-apis-programmatic.html | 161 +++++++++++ .../single-axis-scroll-into-view-expected.txt | 3 + ...gle-axis-scroll-into-view-rtl-expected.txt | 3 + .../single-axis-scroll-into-view-rtl.html | 77 +++++ .../single-axis-scroll-into-view.html | 76 +++++ .../text-overflow-001-expected.html | 0 .../text-overflow-001.html | 2 +- .../text-overflow-002-expected.html | 0 .../text-overflow-002.html | 2 +- .../text-overflow-003-expected.html | 0 .../text-overflow-003.html | 2 +- .../text-overflow-004-expected.html | 0 .../text-overflow-004.html | 2 +- .../text-overflow-005-expected.html | 0 .../text-overflow-005.html | 2 +- .../text-overflow-006-expected.html | 0 .../text-overflow-006.html | 2 +- .../text-overflow-007-expected.xht | 19 ++ .../text-overflow-007.html | 2 +- .../text-overflow-008-expected.html | 0 .../text-overflow-008.html | 6 +- .../text-overflow-009-expected.xht | 19 ++ .../text-overflow-009.html | 2 +- .../text-overflow-010-expected.xht | 19 ++ .../text-overflow-010.html | 2 +- .../text-overflow-011-expected.xht | 19 ++ .../text-overflow-011.html | 2 +- .../text-overflow-012-expected.html | 2 +- .../text-overflow-012.html | 2 +- .../text-overflow-013-expected.html | 0 .../text-overflow-013.html | 2 +- .../text-overflow-014-expected.xht | 19 ++ .../text-overflow-014.html | 2 +- .../text-overflow-015-expected.xht | 19 ++ .../text-overflow-015.html | 2 +- .../text-overflow-016-expected.html | 0 .../text-overflow-016.html | 2 +- .../text-overflow-017-expected.txt | 0 .../text-overflow-017.html | 2 +- .../text-overflow-020-expected.xht | 19 ++ .../text-overflow-020.html | 2 +- .../text-overflow-021-expected.html | 0 .../text-overflow-021.html | 2 +- .../text-overflow-022-expected.html | 2 +- .../text-overflow-022.html | 2 +- .../text-overflow-023-expected.txt | 0 .../text-overflow-023.html | 2 +- .../text-overflow-024-expected.html | 0 .../text-overflow-024-ref.html | 0 .../text-overflow-024.html | 2 +- .../text-overflow-025-expected.html | 0 .../text-overflow-025-ref.html | 0 .../text-overflow-025.html | 2 +- .../text-overflow-026-expected.html | 0 .../text-overflow-026-ref.html | 0 .../text-overflow-026.html | 2 +- .../text-overflow-027-expected.html | 0 .../text-overflow-027.html | 3 +- .../text-overflow-028-expected.html | 0 .../text-overflow-028.html | 3 +- .../text-overflow-029-expected.html | 0 .../text-overflow-029.html | 3 +- .../text-overflow-030-expected.html | 0 .../text-overflow-030.html | 2 +- .../text-overflow-change-color-expected.html | 0 .../text-overflow-change-color.html | 2 +- ...pos-in-inline-block-crash-001-expected.txt | 0 ...psis-abspos-in-inline-block-crash-001.html | 0 ...low-ellipsis-changing-scroll-expected.html | 11 + ...overflow-ellipsis-changing-scroll-ref.html | 11 + ...ext-overflow-ellipsis-changing-scroll.html | 28 ++ ...psis-editable-div-with-caret-expected.html | 29 ++ ...flow-ellipsis-editable-div-with-caret.html | 32 +++ ...text-overflow-ellipsis-hyphen-expected.txt | 0 .../text-overflow-ellipsis-hyphen.html | 2 +- ...overflow-ellipsis-indent-001-expected.html | 0 .../text-overflow-ellipsis-indent-001.html | 2 +- ...rflow-ellipsis-multiline-001-expected.html | 0 .../text-overflow-ellipsis-multiline-001.html | 2 +- ...erflow-ellipsis-self-painting-expected.txt | 0 .../text-overflow-ellipsis-self-painting.html | 2 +- ...ellipsis-textarea-with-caret-expected.html | 25 ++ ...overflow-ellipsis-textarea-with-caret.html | 28 ++ ...t-overflow-ellipsis-width-001-expected.txt | 0 .../text-overflow-ellipsis-width-001.html | 2 +- .../css-overflow/text-overflow-expected.html | 17 ++ .../css/css-overflow/text-overflow-ref.html | 17 ++ .../text-overflow-ruby-expected.html | 0 .../text-overflow-ruby-ref.html | 0 .../text-overflow-ruby.html | 2 +- .../text-overflow-string-001-expected.html | 0 .../text-overflow-string-001.html | 0 .../text-overflow-string-002-expected.html | 0 .../text-overflow-string-002.html | 0 .../text-overflow-string-003-expected.html | 0 .../text-overflow-string-003.html | 4 +- .../text-overflow-string-004-expected.html | 0 .../text-overflow-string-004.html | 0 .../text-overflow-string-005-expected.html | 0 .../text-overflow-string-005.html | 0 .../text-overflow-string-006-expected.html | 0 .../text-overflow-string-006.html | 0 .../text-overflow-string-007-expected.html | 0 .../text-overflow-string-007.html | 0 .../text-overflow-string-008-expected.html | 0 .../text-overflow-string-008.html | 0 .../text-overflow-string-009-expected.html | 25 ++ .../text-overflow-string-009.html | 34 +++ .../text-overflow-string-010-expected.html | 25 ++ .../text-overflow-string-010.html | 34 +++ .../text-overflow-string-011-expected.html | 21 ++ .../text-overflow-string-011.html | 30 ++ .../text-overflow-string-012-expected.html | 29 ++ .../text-overflow-string-012.html | 38 +++ .../text-overflow-string-013-expected.html | 25 ++ .../text-overflow-string-013.html | 34 +++ .../text-overflow-string-014-expected.html | 25 ++ .../text-overflow-string-014.html | 34 +++ .../text-overflow-string-015-expected.html | 29 ++ .../text-overflow-string-015.html | 38 +++ .../text-overflow-string-016-expected.html | 21 ++ .../text-overflow-string-016.html | 30 ++ .../text-overflow-string-017-expected.html | 21 ++ .../text-overflow-string-017.html | 29 ++ .../text-overflow-string-018-expected.html | 21 ++ .../text-overflow-string-018.html | 31 ++ .../text-overflow-string-019-expected.html | 21 ++ .../text-overflow-string-019.html | 31 ++ .../text-overflow-string-020-expected.html | 21 ++ .../text-overflow-string-020.html | 30 ++ .../text-overflow-string-021-expected.html | 21 ++ .../text-overflow-string-021.html | 31 ++ .../text-overflow-string-022-expected.html | 21 ++ .../text-overflow-string-022.html | 30 ++ .../text-overflow-string-023-expected.html | 21 ++ .../text-overflow-string-023.html | 31 ++ ...verflow-string-024.tentative-expected.html | 21 ++ .../text-overflow-string-024.tentative.html | 29 ++ ...verflow-string-025.tentative-expected.html | 21 ++ .../text-overflow-string-025.tentative.html | 29 ++ ...verflow-string-026.tentative-expected.html | 21 ++ .../text-overflow-string-026.tentative.html | 29 ++ ...text-overflow-with-selection-expected.html | 0 .../text-overflow-with-selection-ref.html | 0 .../text-overflow-with-selection.html | 0 .../css/css-overflow/text-overflow.html | 29 ++ ...di-plaintext-scroll-direction-expected.txt | 16 ++ ...icode-bidi-plaintext-scroll-direction.html | 56 ++++ .../css/css-overflow/w3c-import.log | 269 +++++++++++++++++- .../css/css-ui/WEB_FEATURES.yml | 2 - .../css/css-ui/animation/w3c-import.log | 1 + .../css/css-ui/crashtests/w3c-import.log | 2 - .../css/css-ui/parsing/WEB_FEATURES.yml | 1 - .../text-overflow-computed-expected.txt | 4 - .../parsing/text-overflow-computed.html | 19 -- .../text-overflow-invalid-expected.txt | 4 - .../css-ui/parsing/text-overflow-invalid.html | 19 -- .../parsing/text-overflow-valid-expected.txt | 4 - .../css-ui/parsing/text-overflow-valid.html | 19 -- .../css/css-ui/parsing/w3c-import.log | 3 - .../css/css-ui/reference/w3c-import.log | 25 -- .../tentative/button-user-select-expected.txt | 4 + .../css-ui/tentative/button-user-select.html | 17 ++ .../css/css-ui/tentative/w3c-import.log | 1 + .../css/css-ui/text-overflow-expected.html | 17 -- .../css/css-ui/text-overflow-ref.html | 17 -- .../css/css-ui/text-overflow.html | 29 -- .../css/css-ui/user-select-001.html | 4 +- .../css-ui/user-select-button-expected.txt | 2 +- .../css/css-ui/user-select-button.html | 6 +- .../css-ui/user-select-none-in-editable.html | 2 +- .../css/css-ui/user-select-none-on-input.html | 2 +- .../css/css-ui/w3c-import.log | 94 +----- LayoutTests/platform/glib/TestExpectations | 4 +- LayoutTests/platform/ios/TestExpectations | 20 +- LayoutTests/platform/mac/TestExpectations | 4 +- LayoutTests/platform/wpe/TestExpectations | 6 +- 476 files changed, 7214 insertions(+), 1009 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/chrome-480554290-crash.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/crashtests/WEB_FEATURES.yml (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/crashtests/text-overflow-ellipsis-multiline-crash.html (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/crashtests/w3c-import.log create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection.html rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{block-ellipsis-032.tentative-expected.html => block-ellipsis-032-expected.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{block-ellipsis-032.tentative.html => block-ellipsis-032.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui/text-overflow-007-expected.xht => css-overflow/line-clamp/block-ellipsis-033-expected.xht} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-033.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-034-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-034.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-035-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-035.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui/text-overflow-009-expected.xht => css-overflow/line-clamp/block-ellipsis-036-expected.xht} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-036.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-037-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-037.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-038-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-038.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-039-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-039.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui/text-overflow-010-expected.xht => css-overflow/line-clamp/block-ellipsis-040-expected.xht} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-040.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-041-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-041.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-001-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-001.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-002-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-002.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-003-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-003.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-004-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-004.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-crash-001.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui/text-overflow-011-expected.xht => css-overflow/line-clamp/block-ellipsis-quirk-001-expected.xht} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-001.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui/text-overflow-014-expected.xht => css-overflow/line-clamp/block-ellipsis-quirk-002-expected.xht} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-002.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-039-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-039.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-040-crash.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-041-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-041.html rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-003.tentative-expected.html => line-clamp-042-expected.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-009.tentative.html => line-clamp-042.html} (53%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-004.tentative-expected.html => line-clamp-auto-042-expected.html} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-042.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-043-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-043.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-044-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-044.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-045-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-045.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-046-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-046.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-047-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-047.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc.html rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-001.tentative-expected.html => line-clamp-with-floats-001-expected.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-001.tentative.html => line-clamp-with-floats-001.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-002.tentative-expected.html => line-clamp-with-floats-002-expected.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-002.tentative.html => line-clamp-with-floats-002.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-009.tentative-expected.html => line-clamp-with-floats-003-expected.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-003.tentative.html => line-clamp-with-floats-003.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{reference/line-clamp-with-floats-008-ref.html => line-clamp-with-floats-004-expected.html} (58%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-004.tentative.html => line-clamp-with-floats-004.html} (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-005.tentative-expected.html => line-clamp-with-floats-005-expected.html} (78%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-005.tentative.html => line-clamp-with-floats-005.html} (83%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-008-expected.html => line-clamp-with-floats-006-expected.html} (69%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-006.tentative.html => line-clamp-with-floats-006.html} (77%) delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-006.tentative-expected.html rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-007.tentative-expected.html => line-clamp-with-floats-007-expected.html} (67%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-007.tentative.html => line-clamp-with-floats-007.html} (90%) delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-008.html rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-010.tentative-expected.html => line-clamp-with-floats-010-expected.html} (65%) rename LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/{line-clamp-with-floats-010.tentative.html => line-clamp-with-floats-010.html} (70%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui/text-overflow-015-expected.xht => css-overflow/line-clamp/line-clamp-with-floats-011-expected.xht} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-011.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui/text-overflow-020-expected.xht => css-overflow/line-clamp/line-clamp-with-floats-012-expected.xht} (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-034-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-035-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-038-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-039-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-041-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-002-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-003-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-004-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-039-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-041-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-043-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-044-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-045-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-046-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-047-ref.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-006-ref.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044-expected.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047-expected.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-clamps-and-ignores-scroll-offsets-vertical-rl-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-clamps-and-ignores-scroll-offsets-vertical-rl.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-video-hidden-expected-mismatch.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-video-hidden.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/getComputedStyle-scroll-button-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/getComputedStyle-scroll-button.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/before-after-pseudo-element-scrolling-ref.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-001-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-002-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-005-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-006-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-008-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-012-ref.html (95%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-013-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-016-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-021-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-022-ref.html (93%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-027-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-028-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-029-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-030-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-change-color-ref.html (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-editable-div-with-caret-ref.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-ellipsis-indent-001-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-ellipsis-multiline-001-ref.html (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-textarea-with-caret-ref.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-001-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-002-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-003-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-004-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-005-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-006-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-007-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/reference/text-overflow-string-008-ref.html (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-009-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-010-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-011-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-012-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-013-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-014-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-015-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-016-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-024-ref.tentative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-025-ref.tentative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-026-ref.tentative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yaml create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yml create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-inert-003.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-nested-scrollers.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-014.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-iframe.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scrollbar-gutter-zero-width-crash.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-001-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-001.html (88%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-002-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-002.html (88%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-003-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-003.html (88%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-004-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-004.html (89%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-005-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-005.html (86%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-006-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-006.html (93%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-007-expected.xht rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-007.html (95%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-008-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-008.html (71%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-009-expected.xht rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-009.html (90%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-010-expected.xht rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-010.html (94%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-011-expected.xht rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-011.html (93%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-012-expected.html (95%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-012.html (95%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-013-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-013.html (95%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-014-expected.xht rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-014.html (93%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-015-expected.xht rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-015.html (91%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-016-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-016.html (94%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-017-expected.txt (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-017.html (93%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-020-expected.xht rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-020.html (90%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-021-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-021.html (92%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-022-expected.html (93%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-022.html (94%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-023-expected.txt (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-023.html (95%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-024-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-024-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-024.html (87%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-025-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-025-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-025.html (87%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-026-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-026-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-026.html (93%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-027-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-027.html (83%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-028-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-028.html (83%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-029-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-029.html (84%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-030-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-030.html (89%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-change-color-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-change-color.html (86%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-abspos-in-inline-block-crash-001-expected.txt (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-abspos-in-inline-block-crash-001.html (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-changing-scroll-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-changing-scroll-ref.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-changing-scroll.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-editable-div-with-caret-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-editable-div-with-caret.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-hyphen-expected.txt (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-hyphen.html (83%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-indent-001-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-indent-001.html (87%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-multiline-001-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-multiline-001.html (79%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-self-painting-expected.txt (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-self-painting.html (92%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-textarea-with-caret-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-textarea-with-caret.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-width-001-expected.txt (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ellipsis-width-001.html (90%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ref.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ruby-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ruby-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-ruby.html (96%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-001-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-001.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-002-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-002.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-003-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-003.html (89%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-004-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-004.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-005-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-005.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-006-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-006.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-007-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-007.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-008-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-string-008.html (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-009-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-009.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-010-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-010.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-011-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-011.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-012-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-012.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-013-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-013.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-014-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-014.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-015-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-015.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-016-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-016.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-017-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-017.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-018-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-018.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-019-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-019.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-020-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-020.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-021-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-021.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-022-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-022.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-023-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-023.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-024.tentative-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-024.tentative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-025.tentative-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-025.tentative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-026.tentative-expected.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-026.tentative.html rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-with-selection-expected.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-with-selection-ref.html (100%) rename LayoutTests/imported/w3c/web-platform-tests/css/{css-ui => css-overflow}/text-overflow-with-selection.html (100%) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/unicode-bidi-plaintext-scroll-direction-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/unicode-bidi-plaintext-scroll-direction.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/parsing/text-overflow-computed-expected.txt delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/parsing/text-overflow-computed.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/parsing/text-overflow-invalid-expected.txt delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/parsing/text-overflow-invalid.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/parsing/text-overflow-valid-expected.txt delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/parsing/text-overflow-valid.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/tentative/button-user-select-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/tentative/button-user-select.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-expected.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-ref.html delete mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow.html diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index d4790eaa4cfe..c1506a97a05b 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -1370,7 +1370,6 @@ webkit.org/b/309872 imported/w3c/web-platform-tests/permissions-policy/reporting webkit.org/b/182292 imported/w3c/web-platform-tests/css/cssom-view/scrollingElement-quirks-dynamic-001.html [ ImageOnlyFailure ] webkit.org/b/182292 imported/w3c/web-platform-tests/css/cssom-view/scrollingElement-quirks-dynamic-002.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-ui/text-overflow-015.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/eventsource/dedicated-worker/eventsource-constructor-non-same-origin.htm [ Skip ] imported/w3c/web-platform-tests/mediacapture-fromelement/capture.html [ Failure ] imported/w3c/web-platform-tests/mediacapture-fromelement/ended.html [ Failure ] @@ -4186,10 +4185,6 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-021. imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-005.tentative.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-006.tentative.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-007.tentative.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.tentative.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-008.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-011.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-012.html [ ImageOnlyFailure ] @@ -4226,7 +4221,6 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-027.h imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-028.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-029.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-030.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-032.tentative.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-repaint-003.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/continue-001.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/discard/discard-multicol-001.html [ ImageOnlyFailure ] @@ -4241,7 +4235,6 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-011.html imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-019.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-021.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-030.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-033.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-034.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-035.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-036.html [ ImageOnlyFailure ] @@ -4303,14 +4296,10 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixe imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-015.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-016.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-017.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-004.tentative.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-008.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-040.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-045.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-048.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-050.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-051.html [ ImageOnlyFailure ] @@ -4318,6 +4307,60 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-05 imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-053.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-032.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-033.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-035.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-036.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-037.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-039.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-040.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-041.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-002.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-003.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-004.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-002.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-033.html [ Skip ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-041.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-042.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-043.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-044.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-045.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-046.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-047.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-004.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-005.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-006.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-007.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-011.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-editable-div-with-caret.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-016.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-018.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-019.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-020.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-021.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-022.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-024.tentative.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-025.tentative.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-026.tentative.html [ ImageOnlyFailure ] + +imported/w3c/web-platform-tests/css/css-overflow/overflow-video-hidden.html [ Skip ] +imported/w3c/web-platform-tests/css/css-overflow/unicode-bidi-plaintext-scroll-direction.html [ Skip ] + # Fail to run due to rdar://169497013 imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-026.html [ Skip ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-027.html [ Skip ] @@ -5378,18 +5421,14 @@ webkit.org/b/299202 imported/w3c/web-platform-tests/css/css-writing-modes/wm-pro webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/appearance-menulist-button-002.tentative.html [ ImageOnlyFailure ] webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/outline-025.html [ ImageOnlyFailure ] webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/outline-026.html [ ImageOnlyFailure ] -webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/text-overflow-ruby.html [ ImageOnlyFailure ] -webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/text-overflow-021.html [ ImageOnlyFailure ] webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/webkit-appearance-menulist-button-002.tentative.html [ ImageOnlyFailure ] webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/resize-child-will-change-transform.html [ ImageOnlyFailure ] webkit.org/b/279302 imported/w3c/web-platform-tests/css/css-ui/negative-outline-offset.html [ ImageOnlyFailure ] -webkit.org/b/279302 imported/w3c/web-platform-tests/css/css-ui/text-overflow-028.html [ ImageOnlyFailure ] # New failure after import of css/css-ui (2026-07): webkit.org/b/320474 imported/w3c/web-platform-tests/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ ImageOnlyFailure ] webkit.org/b/320474 imported/w3c/web-platform-tests/css/css-ui/resize-textarea-relative-to-right-001.tentative.html [ Pass Failure ] -webkit.org/b/320474 imported/w3c/web-platform-tests/css/css-ui/text-overflow-ellipsis-multiline-001.html [ ImageOnlyFailure ] # Missing CSS-UI-4 caret properties: webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-bar-shape-text-color.html [ ImageOnlyFailure ] @@ -5421,8 +5460,24 @@ webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-zoom.html [ ImageOnlyFailure ] webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-underscore-001.html [ ImageOnlyFailure ] +# text-overflow failures +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-015.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ruby.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-021.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-028.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-multiline-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-editing-input.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-vertical-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-vertical-rtl-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-rtl-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-lr-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-lr-rtl-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-rl-001.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-rl-rtl-001.html [ ImageOnlyFailure ] + # text-overflow should take in account unicode-bidi to determine ellipsis position. -imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-008.html +imported/w3c/web-platform-tests/css/css-overflow/text-overflow-string-008.html webkit.org/b/214387 imported/w3c/web-platform-tests/svg/animations/seeking-events-4.html [ Pass Failure ] @@ -5710,16 +5765,6 @@ webkit.org/b/277262 [ Debug ] imported/w3c/web-platform-tests/css/css-multicol/m # -- End CSS multicol -- # -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-editing-input.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-vertical-001.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-ellipsis-vertical-rtl-001.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-001.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-rtl-001.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-lr-001.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-lr-rtl-001.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-rl-001.html [ ImageOnlyFailure ] -webkit.org/b/214459 imported/w3c/web-platform-tests/css/css-overflow/text-overflow-scroll-vertical-rl-rtl-001.html [ ImageOnlyFailure ] - webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/active-selection-051.html [ ImageOnlyFailure ] webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/active-selection-052.html [ ImageOnlyFailure ] webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/active-selection-053.html [ ImageOnlyFailure ] diff --git a/LayoutTests/imported/w3c/resources/resource-files.json b/LayoutTests/imported/w3c/resources/resource-files.json index 93ef4ea00e45..de0958ad2ab8 100644 --- a/LayoutTests/imported/w3c/resources/resource-files.json +++ b/LayoutTests/imported/w3c/resources/resource-files.json @@ -7231,6 +7231,8 @@ "web-platform-tests/css/css-nesting/nesting-basic-ref.html", "web-platform-tests/css/css-nesting/supports-is-consistent-ref.html", "web-platform-tests/css/css-nesting/supports-rule-ref.html", + "web-platform-tests/css/css-overflow/abspos-shrink-to-fit-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/button-with-scrollable-descendant-ref.html", "web-platform-tests/css/css-overflow/clip-001-ref.html", "web-platform-tests/css/css-overflow/clip-002-ref.html", "web-platform-tests/css/css-overflow/clip-003-ref.html", @@ -7239,12 +7241,24 @@ "web-platform-tests/css/css-overflow/clipped-scroller-add-content-ref.html", "web-platform-tests/css/css-overflow/display-flex-svg-overflow-default-ref.html", "web-platform-tests/css/css-overflow/document-element-overflow-hidden-scroll-ref.html", + "web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002-ref.html", + "web-platform-tests/css/css-overflow/fit-content-textarea-with-scrollbar-ref.html", + "web-platform-tests/css/css-overflow/flex-column-container-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/flex-container-multiple-items-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/flex-container-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/flex-nested-container-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/float-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/grid-container-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/grid-nested-container-with-scrollable-descendant-ref.html", "web-platform-tests/css/css-overflow/incremental-scroll-002-ref.html", "web-platform-tests/css/css-overflow/incremental-scroll-ref.html", + "web-platform-tests/css/css-overflow/inline-block-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-crash-001.html", "web-platform-tests/css/css-overflow/line-clamp/discard/reference/discard-multicol-001-ref.html", "web-platform-tests/css/css-overflow/line-clamp/discard/reference/discard-multicol-002-ref.html", "web-platform-tests/css/css-overflow/line-clamp/discard/reference/discard-multicol-003-ref.html", "web-platform-tests/css/css-overflow/line-clamp/discard/reference/discard-multicol-004-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc-ref.html", "web-platform-tests/css/css-overflow/line-clamp/line-clamp-content-height-with-dynamic-change-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-001-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-002-ref.html", @@ -7268,6 +7282,14 @@ "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-029-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-031-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-032-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-034-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-035-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-038-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-039-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-041-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-002-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-003-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-004-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-repaint-001-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-repaint-002-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-repaint-003-ref.html", @@ -7285,6 +7307,8 @@ "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-027-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-028-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-029-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-039-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-041-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-002-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-005-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-011-ref.html", @@ -7307,6 +7331,11 @@ "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-039-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-040-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-041-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-043-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-044-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-045-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-046-ref.html", + "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-047-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-with-ruby-001-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-with-ruby-003-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-with-ruby-005-ref.html", @@ -7330,9 +7359,7 @@ "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-abspos-023-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-001-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-005-ref.html", - "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-006-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-007-ref.html", - "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-008-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-010-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-text-overflow-string-003-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/webkit-line-clamp-001-ref.html", @@ -7380,6 +7407,14 @@ "web-platform-tests/css/css-overflow/line-clamp/reference/webkit-line-clamp-block-in-inline-001-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/webkit-line-clamp-dynamic-001-ref.html", "web-platform-tests/css/css-overflow/line-clamp/reference/webkit-line-clamp-with-line-height-ref.html", + "web-platform-tests/css/css-overflow/max-content-nested-textarea-with-scrollbar-ref.html", + "web-platform-tests/css/css-overflow/max-content-textarea-with-scrollbar-ref.html", + "web-platform-tests/css/css-overflow/max-content-with-float-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/max-content-with-multiple-scrollable-descendants-ref.html", + "web-platform-tests/css/css-overflow/max-width-container-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/max-width-container-with-scrollable-descendant-vertical-rl-ref.html", + "web-platform-tests/css/css-overflow/min-content-textarea-with-scrollbar-ref.html", + "web-platform-tests/css/css-overflow/orthogonal-writing-mode-with-scrollable-descendant-ref.html", "web-platform-tests/css/css-overflow/overflow-alignment-001-ref.html", "web-platform-tests/css/css-overflow/overflow-alignment-002-ref.html", "web-platform-tests/css/css-overflow/overflow-alignment-block-001.html", @@ -7439,6 +7474,7 @@ "web-platform-tests/css/css-overflow/overflow-clip-transform-001-ref.html", "web-platform-tests/css/css-overflow/overflow-clip-x-visible-y-svg-ref.html", "web-platform-tests/css/css-overflow/overflow-clip-y-visible-x-svg-ref.html", + "web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-ref.html", "web-platform-tests/css/css-overflow/overflow-img-display-table-ref.html", "web-platform-tests/css/css-overflow/overflow-img-object-position-ref.html", "web-platform-tests/css/css-overflow/overflow-img-ref.html", @@ -7452,6 +7488,7 @@ "web-platform-tests/css/css-overflow/overflow-scroll-resize-visibility-hidden-ref.html", "web-platform-tests/css/css-overflow/overflow-video-ref.html", "web-platform-tests/css/css-overflow/paint-containment-svg-ref.html", + "web-platform-tests/css/css-overflow/reference/before-after-pseudo-element-scrolling-ref.html", "web-platform-tests/css/css-overflow/reference/input-scrollable-region-001-ref.html", "web-platform-tests/css/css-overflow/reference/overflow-body-no-propagation-ref.html", "web-platform-tests/css/css-overflow/reference/overflow-body-propagation-ref.html", @@ -7459,15 +7496,53 @@ "web-platform-tests/css/css-overflow/reference/overflow-inline-block-with-opacity-ref.html", "web-platform-tests/css/css-overflow/reference/overflow-recalc-001-ref.html", "web-platform-tests/css/css-overflow/reference/ref-if-there-is-no-red.xht", + "web-platform-tests/css/css-overflow/reference/text-overflow-001-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-002-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-005-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-006-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-008-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-012-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-013-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-016-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-021-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-022-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-027-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-028-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-029-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-030-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-change-color-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-001-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-002-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-editable-div-with-caret-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-indent-001-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-multiline-001-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-rtl-001-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-textarea-with-caret-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-vertical-001-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-vertical-rtl-001-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-scroll-001-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-scroll-rtl-001-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-scroll-vertical-lr-001-ref.html", "web-platform-tests/css/css-overflow/reference/text-overflow-scroll-vertical-lr-rtl-001-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-001-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-002-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-003-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-004-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-005-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-006-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-007-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-008-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-009-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-010-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-011-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-012-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-013-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-014-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-015-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-016-ref.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-024-ref.tentative.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-025-ref.tentative.html", + "web-platform-tests/css/css-overflow/reference/text-overflow-string-026-ref.tentative.html", "web-platform-tests/css/css-overflow/rounded-overflow-clip-visible-ref.html", "web-platform-tests/css/css-overflow/rounded-overflow-visible-clip-ref.html", "web-platform-tests/css/css-overflow/scroll-markers/column-scroll-marker-001-ref.html", @@ -7480,6 +7555,7 @@ "web-platform-tests/css/css-overflow/scroll-markers/root-scroll-button-ref.html", "web-platform-tests/css/css-overflow/scroll-markers/root-scroll-marker-ref.html", "web-platform-tests/css/css-overflow/scroll-markers/scroll-button-on-object-ref.html", + "web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-ref.html", "web-platform-tests/css/css-overflow/scroll-markers/scroll-buttons-001-ref.html", "web-platform-tests/css/css-overflow/scroll-markers/scroll-buttons-appearance-ref.html", "web-platform-tests/css/css-overflow/scroll-markers/scroll-buttons-disabled-ref.html", @@ -7544,8 +7620,19 @@ "web-platform-tests/css/css-overflow/scrollbar-large-scale-in-iframe-ref.html", "web-platform-tests/css/css-overflow/scrollbars-chrome-bug-001-ref.html", "web-platform-tests/css/css-overflow/select-size-overflow-001-ref.html", + "web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-ref.html", + "web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-ref.html", + "web-platform-tests/css/css-overflow/table-max-content-with-scrollable-descendant-ref.html", + "web-platform-tests/css/css-overflow/text-overflow-024-ref.html", + "web-platform-tests/css/css-overflow/text-overflow-025-ref.html", + "web-platform-tests/css/css-overflow/text-overflow-026-ref.html", "web-platform-tests/css/css-overflow/text-overflow-ellipsis-003-ref.html", + "web-platform-tests/css/css-overflow/text-overflow-ellipsis-changing-scroll-ref.html", "web-platform-tests/css/css-overflow/text-overflow-ellipsis-editing-input-ref.html", + "web-platform-tests/css/css-overflow/text-overflow-ref.html", + "web-platform-tests/css/css-overflow/text-overflow-ruby-ref.html", + "web-platform-tests/css/css-overflow/text-overflow-string-in-input-notref.html", + "web-platform-tests/css/css-overflow/text-overflow-with-selection-ref.html", "web-platform-tests/css/css-position/absolute-pos-box-inside-fixed-pos-box-with-changing-height-ref.html", "web-platform-tests/css/css-position/backdrop-inherit-rendered-ref.html", "web-platform-tests/css/css-position/block-axis-constraint-changes-for-out-of-flow-box-ref.html", @@ -10040,31 +10127,6 @@ "web-platform-tests/css/css-ui/reference/outline-style-014-ref.html", "web-platform-tests/css/css-ui/reference/outline-with-padding-001-ref.html", "web-platform-tests/css/css-ui/reference/subpixel-outline-width-ref.tentative.html", - "web-platform-tests/css/css-ui/reference/text-overflow-001-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-002-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-005-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-006-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-008-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-012-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-013-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-016-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-021-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-022-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-027-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-028-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-029-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-030-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-change-color-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-ellipsis-indent-001-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-ellipsis-multiline-001-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-001-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-002-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-003-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-004-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-005-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-006-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-007-ref.html", - "web-platform-tests/css/css-ui/reference/text-overflow-string-008-ref.html", "web-platform-tests/css/css-ui/reference/transparent-accent-color-001-ref.html", "web-platform-tests/css/css-ui/reference/transparent-accent-color-002-ref.html", "web-platform-tests/css/css-ui/resize-change-margin-ref.html", @@ -10104,12 +10166,6 @@ "web-platform-tests/css/css-ui/support/w100.svg", "web-platform-tests/css/css-ui/support/w100_h100.svg", "web-platform-tests/css/css-ui/support/w100_r1-1.svg", - "web-platform-tests/css/css-ui/text-overflow-024-ref.html", - "web-platform-tests/css/css-ui/text-overflow-025-ref.html", - "web-platform-tests/css/css-ui/text-overflow-026-ref.html", - "web-platform-tests/css/css-ui/text-overflow-ref.html", - "web-platform-tests/css/css-ui/text-overflow-ruby-ref.html", - "web-platform-tests/css/css-ui/text-overflow-with-selection-ref.html", "web-platform-tests/css/css-ui/translucent-outline-ref.html", "web-platform-tests/css/css-ui/widget-percentage-height-001-ref.html", "web-platform-tests/css/css-values/attr-in-slotted-ref.html", diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/WEB_FEATURES.yml b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/WEB_FEATURES.yml index c68c9faebee6..282654533f72 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/WEB_FEATURES.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/WEB_FEATURES.yml @@ -1,34 +1,17 @@ -features: -- name: scrollbar-gutter - files: - - scrollbar-gutter-* -- name: overflow-overlay - files: - - overflow-overlay.html -- name: overflow-clip-margin - files: - - overflow-clip-margin-* - - overflow-no-interpolation.html - - paint-containment-svg.html -- name: overflow-clip - files: - - clip-* - - overflow-clip-* - - "!overflow-clip-margin-*" - - dynamic-visible-to-clip-001.html - - rounded-overflow-clip-visible.html -- name: overflow-shorthand - files: - - overflow-* - - "!overflow-auto-scrollbar-gutter-intrinsic-*" - - "!overflow-scroll-*" - - "!overflow-no-interpolation.html" # depends on transition-behavior - - "!overflow-ellipsis-dynamic-001.html" - - "!overflow-clip-*" -- name: column-pseudo - files: - - column-* -- name: text-overflow - files: - - text-overflow-* - - overflow-ellipsis-dynamic-001.html +rules: +- scrollbar-gutter-*: [scrollbar-gutter] +- overflow-overlay.html: [overflow-overlay, overflow-shorthand] +- overflow-clip-margin-*: [overflow-clip-margin] +- overflow-no-interpolation.html: [overflow-clip-margin] # depends on transition-behavior +- paint-containment-svg.html: [overflow-clip-margin] +- clip-*: [overflow-clip] +- overflow-clip-*: [overflow-clip] +- dynamic-visible-to-clip-001.html: [overflow-clip] +- rounded-overflow-clip-visible.html: [overflow-clip] +- overflow-auto-scrollbar-gutter-intrinsic-*: [] +- overflow-scroll-*: [] +- overflow-ellipsis-dynamic-001.html: [text-overflow] +- overflow-*: [overflow-shorthand] +- column-*: [column-pseudo] +- text-overflow-*: [text-overflow] +- text-overflow-string-*: [custom-ellipses, text-overflow] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling-expected.html new file mode 100644 index 000000000000..b4c8db95f3e6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling-expected.html @@ -0,0 +1,6 @@ + + +CSS Reftest Reference + +

Test passes if there is a filled green square and no red.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling.html new file mode 100644 index 000000000000..805c01af85b7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/before-after-pseudo-element-scrolling.html @@ -0,0 +1,59 @@ + + + + Scrolls of `::before` and `::after` pseudo element should persist after a reflow. + + + + + + + + + + +
dummy
+

Test passes if there is a filled green square and no red.

+
+
+ + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/chrome-480554290-crash.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/chrome-480554290-crash.html new file mode 100644 index 000000000000..830ebd5209b2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/chrome-480554290-crash.html @@ -0,0 +1,30 @@ + + +Chrome crash 480554290 + +
+
+
+
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html index cec5c7b6e647..cd8b9a3cbeb6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html @@ -4,7 +4,6 @@ - +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002-ref.html new file mode 100644 index 000000000000..359a6e7dc919 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002-ref.html @@ -0,0 +1,12 @@ + + +CSS Test Reference: viewport with overflow: clip on the root element + +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002.html new file mode 100644 index 000000000000..18b6205aca29 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/dynamic-visible-to-clip-002.html @@ -0,0 +1,24 @@ + + + +The viewport scrollbar is updated when the root element's overflow is dynamically changed to 'clip' + + + + + +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection-expected.txt new file mode 100644 index 000000000000..8ee065a02d54 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection-expected.txt @@ -0,0 +1,4 @@ + +PASS Near-Z element projected outside border-radius is clipped +FAIL Corner point outside border-radius hits body after perspective assert_not_equals: got disallowed value Element node
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection.html new file mode 100644 index 000000000000..227288e591f5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-border-radius-and-perspective-projection.html @@ -0,0 +1,63 @@ + +Hit Test border-radius clipping with perspective + + + + + + + + +
+
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-stacking-context-parent-border-radius-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-stacking-context-parent-border-radius-expected.txt index 98840b9cb2b3..338f82e89918 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-stacking-context-parent-border-radius-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/hit-test-stacking-context-parent-border-radius-expected.txt @@ -1,18 +1,33 @@ -FAIL Hit testing respects border-radius clipping on elements without creating stacking contexts assert_equals: expected Element node
-
-
- -... but got Element node
-FAIL Hit testing respects border-radius clipping on elements with will-change transform assert_equals: expected Element node
-
-FAIL Hit testing respects border-radius clipping on elements with opacity < 1 assert_equals: expected Element node
-
-FAIL Hit testing respects border-radius clipping on elements with a transform property assert_equals: expected Element node
-
-
-
+
+
+FAIL Hit testing respects border-radius clipping on elements without creating stacking contexts with perspective assert_equals: expected Element node
+
+FAIL Hit testing respects border-radius clipping on elements with will-change transform assert_equals: expected Element node
+
+
+FAIL Hit testing respects border-radius clipping on elements with will-change transform with perspective assert_equals: expected Element node
+
+FAIL Hit testing respects border-radius clipping on elements with opacity < 1 assert_equals: expected Element node
+
+
+FAIL Hit testing respects border-radius clipping on elements with opacity < 1 with perspective assert_equals: expected Element node
+
+FAIL Hit testing respects border-radius clipping on elements with a transform property assert_equals: expected Element node
+
+
+
+
+
+
+
+
+
-
+
+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance-expected.txt index dfda29631576..842aa1fe15d8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance-expected.txt @@ -1,9 +1,9 @@ PASS Property block-ellipsis has initial value no-ellipsis -PASS Property block-ellipsis inherits -PASS Property continue has initial value auto +FAIL Property block-ellipsis inherits assert_equals: expected "ellipsis" but got "no-ellipsis" +FAIL Property continue has initial value normal assert_equals: expected "normal" but got "auto" FAIL Property continue does not inherit assert_equals: expected "collapse" but got "auto" -PASS Property max-lines has initial value none +FAIL Property max-lines has initial value auto assert_equals: expected "auto" but got "none" PASS Property max-lines does not inherit PASS Property overflow-block has initial value visible PASS Property overflow-block does not inherit diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance.html index ced9fa1b6995..e817ffd771eb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/inheritance.html @@ -15,9 +15,9 @@
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.html similarity index 70% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.tentative.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.html index a00ff6017125..c74f75d9e66c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.tentative.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.html @@ -1,13 +1,13 @@ -CSS Overflow: line-clamp hidden floats should count as ink overflow +CSS Overflow: line-clamp hidden and clipped floats don't count as scrollable overflow - + + +

Test passes if there is a filled green square and no red. + +

+ +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-020-expected.xht b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012-expected.xht similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-020-expected.xht rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012-expected.xht diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012.html new file mode 100644 index 000000000000..524eec50d3cf --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012.html @@ -0,0 +1,58 @@ + + +CSS Overflow: line-clamp clips floats at the block-end only + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
..X
+
+ AAA + X + K + OOO + P +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-034-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-034-ref.html new file mode 100644 index 000000000000..0b7fb78a9535 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-034-ref.html @@ -0,0 +1,11 @@ + + +Test reference + + + +

Test passes if there is no red) diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-035-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-035-ref.html new file mode 100644 index 000000000000..f50980a2cce3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-035-ref.html @@ -0,0 +1,14 @@ + + +Test reference + + + +

Test passes if there are two lines of text, and the second +line ends with a blue word followed by a closing parenthesis) diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-038-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-038-ref.html new file mode 100644 index 000000000000..5f6dc1ae8990 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-038-ref.html @@ -0,0 +1,10 @@ + + +CSS Overflow: non-empty custom block ellipsis reference + +

First line
PASS CUSTOM
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-039-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-039-ref.html new file mode 100644 index 000000000000..0f44c45f03fe --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-039-ref.html @@ -0,0 +1,27 @@ + + +Test reference + + + +

This test passes if the two boxes below are identical, including having the same height. + +

+ TEST + … +
+
+ TEST + … +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-041-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-041-ref.html new file mode 100644 index 000000000000..2dc26d84ba65 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-041-ref.html @@ -0,0 +1,22 @@ + + +Test reference + + + +

Test passes if there is a “…” below and no red. + +

+ … +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-002-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-002-ref.html new file mode 100644 index 000000000000..76231c06e96f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-002-ref.html @@ -0,0 +1,20 @@ + + +Test reference + + + +

Test passes if there is no red, +and there is an ellipsis character (“…”) on the left side +of the of second line of the text below. + +

+ Story time:
+ "روزی روزگاری… +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-003-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-003-ref.html new file mode 100644 index 000000000000..922d2e9dc505 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-003-ref.html @@ -0,0 +1,28 @@ + + +Test referenec + + + +

Test passes if the text in the blue box matches the one in the orange box. + +

+ Story time:
+ روزی روزگاری… (continued) +
+
+ Story time:
+ روزی روزگاری… (continued) +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-004-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-004-ref.html new file mode 100644 index 000000000000..589212417405 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-004-ref.html @@ -0,0 +1,22 @@ + + +Test reference + + + +

Test passes if the text in the blue box matches the one in the orange box. + +

+ He said "سلام" …آخرہ +
+
+ He said "سلام" …آخرہ +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-011-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-011-ref.html index 02ef71d56193..d318a3e662f8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-011-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-011-ref.html @@ -2,15 +2,17 @@ CSS Reference
Line 1 Line 2 -Line 3…
-

Following content.

+Line 3
+
Line 4…
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html index b6e816f997c2..9c0772c2782b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html @@ -3,6 +3,7 @@ CSS Reference +
+ +

Line 1

+

Line 2

+

Line 3

+ +

Line 4

+

Line 5

+

Line 6

+ +

Line 7

+

Line 8

+

Line 9

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-041-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-041-ref.html new file mode 100644 index 000000000000..1a42019d7869 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-041-ref.html @@ -0,0 +1,15 @@ + + +CSS Reference + +
Line 1 +Line 2 +Line 3…
+

Following content.

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-043-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-043-ref.html new file mode 100644 index 000000000000..885f931e544b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-043-ref.html @@ -0,0 +1,22 @@ + + + +CSS Overflow: test reference + +
+
+ Line 1
+ Line 2
+ Line 3 +
+ Line B… +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-044-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-044-ref.html new file mode 100644 index 000000000000..746cf6446785 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-044-ref.html @@ -0,0 +1,20 @@ + + + +CSS Overflow: test reference + + +
+

Line 1

+

Line 2

+
Line 3
+

Line 4…

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-045-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-045-ref.html new file mode 100644 index 000000000000..9b4fd5625fa7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-045-ref.html @@ -0,0 +1,41 @@ + + + +CSS Overflow: test reference + +
+ Line 1
+ Line 2
+ Line 3
+
+ Line A
+ Line B +
+ Line 4
+
+
+
+
Abspos
+
+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-046-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-046-ref.html new file mode 100644 index 000000000000..705f2b0e3e52 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-046-ref.html @@ -0,0 +1,29 @@ + + + +CSS Overflow: test reference + + +
+
+ Line A
+ Line B
+ Line C
+ Line D
+ Line E
+
+ Line 1
+ Line 2
+ Line 3
+ Line 4… +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-047-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-047-ref.html new file mode 100644 index 000000000000..b934b7e6d2d3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-047-ref.html @@ -0,0 +1,28 @@ + + + +CSS Overflow: test reference + + +
+
+ Line A
+ Line B
+ Line C
+ Line D
+ Line E
+
+ Line 1
+ Line 2… +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-005-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-005-ref.html index d20d6c53ddee..e3ebd7baac6d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-005-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-005-ref.html @@ -2,22 +2,27 @@ CSS Reference +
Line 1 Line 2 Line 3 Line 4…
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-006-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-006-ref.html deleted file mode 100644 index 9288c4e36f92..000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-006-ref.html +++ /dev/null @@ -1,28 +0,0 @@ - - -CSS Reference - -
-
Line 1 -Line 2 -Line 3
-
-
Line 4…
-
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-007-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-007-ref.html index 6d5390246b4d..56a74d934927 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-007-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-007-ref.html @@ -2,25 +2,30 @@ CSS Reference +
Line 1 Line 2 -Line 3 -Line 4…
Line A +Line 3
Line A Line B Line C Line D -Line E
+Line E
+Line 4…
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-010-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-010-ref.html index 12b8cdc441a6..d4fbf4b4dbf2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-010-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-010-ref.html @@ -3,13 +3,15 @@ CSS Reference
+
Line 1 Line 2 Line 3 Line 4…
- - +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/w3c-import.log index 32f977e1da10..2e898babb7de 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/w3c-import.log @@ -10,8 +10,6 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: None -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-001-ref.html @@ -36,6 +34,14 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-029-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-031-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-032-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-034-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-035-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-038-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-039-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-041-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-002-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-003-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-bidi-004-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-repaint-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-repaint-002-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/block-ellipsis-repaint-003-ref.html @@ -53,6 +59,8 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-027-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-028-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-029-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-039-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-041-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-002-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-005-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-011-ref.html @@ -75,6 +83,11 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-039-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-040-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-041-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-043-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-044-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-045-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-046-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-047-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-with-ruby-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-with-ruby-003-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-auto-with-ruby-005-ref.html @@ -98,9 +111,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-abspos-023-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-005-ref.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-006-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-007-ref.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-008-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-floats-010-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/line-clamp-with-text-overflow-string-003-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/reference/webkit-line-clamp-001-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/w3c-import.log index 3494d9b78b7e..f748a716e3b0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/w3c-import.log @@ -10,8 +10,6 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: box-decoration-break -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/WEB_FEATURES.yml @@ -77,8 +75,39 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-030.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-031-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-031.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-032.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-032.tentative.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-032-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-032.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-033-expected.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-033.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-034-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-034.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-035-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-035.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-036-expected.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-036.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-037-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-037.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-038-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-038.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-039-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-039.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-040-expected.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-040.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-041-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-041.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-001-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-001.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-002-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-002.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-003-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-003.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-004-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-bidi-004.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-crash-001.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-001-expected.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-001.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-002-expected.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-quirk-002.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-repaint-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-repaint-001.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-repaint-002-expected.html @@ -165,6 +194,13 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-037.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-038-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-038.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-039-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-039.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-040-crash.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-041-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-041.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-042-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-042.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-001-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-001.html @@ -247,6 +283,18 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-040.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-041-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-041.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-042-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-042.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-043-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-043.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-044-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-044.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-045-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-045.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-046-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-046.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-047-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-047.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-with-ruby-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-with-ruby-001.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-auto-with-ruby-002-expected.html @@ -281,10 +329,12 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-balance-011.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-balance-012-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-balance-012.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-bfc.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-content-height-with-dynamic-change-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-content-height-with-dynamic-change-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-content-height-with-dynamic-change.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-content-height-with-dynamic-change.html.rej /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-abspos-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-abspos-001.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-abspos-002-expected.html @@ -331,6 +381,8 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-abspos-022.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-abspos-023-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-abspos-023.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-block-in-inline-001-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-block-in-inline-001.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-001.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-002-expected.html @@ -365,26 +417,26 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-016.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-017-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-fixed-pos-017.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-001.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-001.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-002.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-002.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-003.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-003.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-004.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-004.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-005.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-005.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-006.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-006.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-007.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-007.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-008-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-008.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-009.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-009.tentative.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.tentative-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.tentative.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-001-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-001.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-002-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-002.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-003-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-003.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-004-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-004.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-005-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-005.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-006-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-006.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-007-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-007.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-010.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-011-expected.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-011.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012-expected.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floats-012.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-text-overflow-string-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-text-overflow-string-001.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-text-overflow-string-002-expected.html @@ -473,14 +525,10 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-042-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-043-expected.xht /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-043.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-045-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-045.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-046-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-046.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-048-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-048.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-049-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044-expected.html deleted file mode 100644 index 25018d4f59e6..000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044-expected.html +++ /dev/null @@ -1,25 +0,0 @@ - - -CSS Test Reference - - -
Line 1 -Line 2 -Line 3 -Line 4 -
Line 5
\ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044.html deleted file mode 100644 index 981e09b466c7..000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044.html +++ /dev/null @@ -1,35 +0,0 @@ - - -CSS Overflow: -webkit-line-clamp creates an IFC - - - - - - -
Line 1 -Line 2 -Line 3 -Line 4 -
Line 5
\ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047-expected.html deleted file mode 100644 index 3f1e31ab2224..000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047-expected.html +++ /dev/null @@ -1,25 +0,0 @@ - - -CSS Test Reference - - -
Line 1 -Line 2 -Line 3 -Line 4 -
Line 5…
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047.html deleted file mode 100644 index cb66eb714c51..000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-047.html +++ /dev/null @@ -1,38 +0,0 @@ - - -CSS Overflow: -webkit-line-clamp creates an IFC - - - - - - - -
Line 1 -Line 2 -Line 3 -Line 4 -
Line 5
-Line 6 -Line 7
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height-expected.html index c9a9ae5d7ffe..d318a3e662f8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height-expected.html @@ -2,16 +2,17 @@ CSS Reference
Line 1 Line 2 -Line 3 -Line 4…
+Line 3
+
Line 4…
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height.html index 410fbef9c751..b07d04fbe651 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height.html @@ -3,19 +3,22 @@ CSS Overflow: -webkit-line-clamp with max-height - - + + @@ -23,6 +26,6 @@ Line 2 Line 3 Line 4 -Line 5 + +Line 7
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/max-width-container-with-scrollable-descendant.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/max-width-container-with-scrollable-descendant.html index da5056fa95f0..14e5232f06a7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/max-width-container-with-scrollable-descendant.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/max-width-container-with-scrollable-descendant.html @@ -1,7 +1,7 @@ - + + +
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-expected.html index 378da736ecfc..f3aa3676f2e8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-expected.html @@ -13,7 +13,7 @@ height: 100px; will-change: transform; background: black; - box-shadow: 10px 50px 5px red; + box-shadow: 50px 50px 5px green; } .cover { diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-ref.html index 378da736ecfc..f3aa3676f2e8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow-ref.html @@ -13,7 +13,7 @@ height: 100px; will-change: transform; background: black; - box-shadow: 10px 50px 5px red; + box-shadow: 50px 50px 5px green; } .cover { diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow.html index 8ea8e2c3334e..3af937a10e00 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-content-visual-overflow.html @@ -19,19 +19,29 @@ width: 100px; height: 100px; background: black; - box-shadow: 10px 50px 5px red; + box-shadow: 50px 50px 5px green; } .spacer { width: 100px; height: 150px; } + + .fail { + width: 10px; + height: 10px; + background: red; + position: absolute; + z-index: -1; + }
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-hit-testing.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-hit-testing.html index b22497601c2a..b819e3dc00a4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-hit-testing.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-hit-testing.html @@ -23,7 +23,7 @@ } .child2 { - background-color: red; + background-color: blue; }
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-expected.html index cf6b55a2f91d..3c69cc7c061c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-expected.html @@ -8,8 +8,8 @@ width: 100px; height: 100px; background-color: green; - box-shadow: 20px 20px 5px red; + box-shadow: 20px 20px 5px blue; } -

You should see a green box with a red box shadow. +

You should see a green box with a blue box shadow.

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-ref.html index cf6b55a2f91d..3c69cc7c061c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003-ref.html @@ -8,8 +8,8 @@ width: 100px; height: 100px; background-color: green; - box-shadow: 20px 20px 5px red; + box-shadow: 20px 20px 5px blue; } -

You should see a green box with a red box shadow. +

You should see a green box with a blue box shadow.

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003.html index 52625e90977f..5b74fefd952e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-003.html @@ -11,8 +11,18 @@ background-color: green; overflow: clip; overflow-clip-margin: 1px; - box-shadow: 20px 20px 5px red; + box-shadow: 20px 20px 5px blue; + } + .fail { + width: 20px; + height: 20px; + background-color: red; + position: relative; + top: -15px; + left: 85px; + z-index: -1; } -

You should see a green box with a red box shadow. +

You should see a green box with a blue box shadow.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-expected.html index cf6b55a2f91d..3c69cc7c061c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-expected.html @@ -8,8 +8,8 @@ width: 100px; height: 100px; background-color: green; - box-shadow: 20px 20px 5px red; + box-shadow: 20px 20px 5px blue; } -

You should see a green box with a red box shadow. +

You should see a green box with a blue box shadow.

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-ref.html index cf6b55a2f91d..3c69cc7c061c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006-ref.html @@ -8,8 +8,8 @@ width: 100px; height: 100px; background-color: green; - box-shadow: 20px 20px 5px red; + box-shadow: 20px 20px 5px blue; } -

You should see a green box with a red box shadow. +

You should see a green box with a blue box shadow.

diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006.html index 493bcee29f65..48602c685a6c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-006.html @@ -11,8 +11,18 @@ background-color: green; contain: paint; overflow-clip-margin: 1px; - box-shadow: 20px 20px 5px red; + box-shadow: 20px 20px 5px blue; + } + .fail { + width: 20px; + height: 20px; + background-color: red; + position: relative; + top: -15px; + left: 85px; + z-index: -1; } -

You should see a green box with a red box shadow. +

You should see a green box with a blue box shadow.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013.html new file mode 100644 index 000000000000..7b9e85e3ca64 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-013.html @@ -0,0 +1,31 @@ + + + +overflow-clip-margin: border-box + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014.html new file mode 100644 index 000000000000..fdd5b4f2568a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-014.html @@ -0,0 +1,32 @@ + + + +overflow-clip-margin: content-box + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015.html new file mode 100644 index 000000000000..3c52d3496691 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-015.html @@ -0,0 +1,31 @@ + + + +overflow-clip-margin: keyword + positive length + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016.html new file mode 100644 index 000000000000..2eb0ced43ea2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-016.html @@ -0,0 +1,33 @@ + + + +overflow-clip-margin: keyword + negative length + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017.html new file mode 100644 index 000000000000..da0623a96c05 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-017.html @@ -0,0 +1,33 @@ + + + +overflow-clip-margin: just a negative length + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018.html new file mode 100644 index 000000000000..fc720d98a058 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-018.html @@ -0,0 +1,32 @@ + + + +overflow-clip-margin: content-box on a scroller + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019.html new file mode 100644 index 000000000000..9219fe38d4a1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-019.html @@ -0,0 +1,33 @@ + + + +overflow-clip-margin: keyword + negative length on a scroller + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020.html new file mode 100644 index 000000000000..90ce18a5d25d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-020.html @@ -0,0 +1,33 @@ + + + +overflow-clip-margin: just a negative length on a scroller + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021.html new file mode 100644 index 000000000000..5ee29821c6b1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-021.html @@ -0,0 +1,34 @@ + + + +overflow-clip-margin: border-box is ignored on a scroller + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022.html new file mode 100644 index 000000000000..f55c80e23b64 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-022.html @@ -0,0 +1,34 @@ + + + +overflow-clip-margin: border-box is ignored on a scroller, including the offset + + + + +

Test passes if there is a filled green square.

+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic-expected.html new file mode 100644 index 000000000000..82fcaa3b2aa4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic-expected.html @@ -0,0 +1,4 @@ + + +

Test passes if there is a filled green square.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic.html new file mode 100644 index 000000000000..be66d7a80c71 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-margin-content-box-dynamic.html @@ -0,0 +1,32 @@ + + + + + + + +

Test passes if there is a filled green square.

+
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar-expected.txt new file mode 100644 index 000000000000..cd0b157c9d84 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar-expected.txt @@ -0,0 +1,3 @@ + +PASS overflow: clip on the off-axis does not display a scrollbar. + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar.html new file mode 100644 index 000000000000..b01ff3b919d2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clip-no-off-axis-scrollbar.html @@ -0,0 +1,42 @@ + + +overflow: clip does not show an off-axis scrollbar + + + + + + +
+
+
+
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-expected.html new file mode 100644 index 000000000000..ef8eed504229 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-expected.html @@ -0,0 +1,35 @@ + + +CSS Reference: overflow clipping with transparent borders + +

Test passes if you see two blue squares with green borders, followed by + a green-blue-green striped rectangle where the blue stripe overflows to the right.

+
+
+
+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-ref.html new file mode 100644 index 000000000000..ef8eed504229 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip-ref.html @@ -0,0 +1,35 @@ + + +CSS Reference: overflow clipping with transparent borders + +

Test passes if you see two blue squares with green borders, followed by + a green-blue-green striped rectangle where the blue stripe overflows to the right.

+
+
+
+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip.html new file mode 100644 index 000000000000..fcda5ab7a96e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-clipped-transparent-border-clip.html @@ -0,0 +1,56 @@ + + +CSS Test: overflow clipping preserves background visible through transparent borders + + + + + +

Test passes if you see two blue squares with green borders, followed by + a green-blue-green striped rectangle where the blue stripe overflows to the right.

+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-hidden-resize-with-stacking-context-child.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-hidden-resize-with-stacking-context-child.html index 8569ac153329..c76cce507aa5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-hidden-resize-with-stacking-context-child.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-hidden-resize-with-stacking-context-child.html @@ -22,6 +22,14 @@ height: 20px; background: green; } +.fail { + width: 20px; + height: 20px; + background: red; + position: relative; + top: -60px; + left: 40px; +}

Test passes if there is a filled green square.

@@ -33,6 +41,7 @@
+
+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-video-hidden.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-video-hidden.html new file mode 100644 index 000000000000..aaa866ca72d9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/overflow-video-hidden.html @@ -0,0 +1,29 @@ + + + +Verifies overflow: hidden applies to video elements + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/WEB_FEATURES.yml b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/WEB_FEATURES.yml index 7db666f28122..773f30e499bf 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/WEB_FEATURES.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/WEB_FEATURES.yml @@ -1,20 +1,9 @@ -features: -- name: overflow-clip-margin - files: - - overflow-clip-margin* -- name: scroll-markers - files: - - scroll-markers* -- name: scroll-buttons - files: - - scroll-button* -- name: scrollbar-gutter - files: - - scrollbar-gutter-* -- name: line-clamp - files: - - line-clamp-* - - webkit-line-clamp-* -- name: text-overflow - files: - - text-overflow-* +rules: +- overflow-clip-margin*: [overflow-clip-margin] +- scroll-markers*: [scroll-markers] +- scroll-button*: [scroll-buttons] +- scroll-target-group*: [scroll-target-group] +- scrollbar-gutter-*: [scrollbar-gutter] +- line-clamp-*: [line-clamp] +- webkit-line-clamp-*: [line-clamp] +- text-overflow-*: [text-overflow] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid-expected.txt index b8b1e2aa7e69..a9047ff04b41 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid-expected.txt @@ -1,9 +1,12 @@ PASS e.style['block-ellipsis'] = "hidden" should not set the property value PASS e.style['block-ellipsis'] = "none" should not set the property value +FAIL e.style['block-ellipsis'] = "auto" should not set the property value assert_equals: expected "" but got "auto" PASS e.style['block-ellipsis'] = "none auto" should not set the property value PASS e.style['block-ellipsis'] = "no-ellipsis auto" should not set the property value +PASS e.style['block-ellipsis'] = "ellipsis auto" should not set the property value PASS e.style['block-ellipsis'] = "auto \"string\"" should not set the property value +PASS e.style['block-ellipsis'] = "ellipsis \"string\"" should not set the property value PASS e.style['block-ellipsis'] = "\"string\" none" should not set the property value PASS e.style['block-ellipsis'] = "\"string\" no-ellipsis" should not set the property value PASS e.style['block-ellipsis'] = "\"first\" \"second\"" should not set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid.html index e9fcca411d90..486ddae01299 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-invalid.html @@ -4,7 +4,7 @@ CSS Overflow: parsing block-ellipsis with invalid values - + @@ -13,10 +13,13 @@ @@ -12,7 +12,7 @@ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid-expected.txt index ebbb5240a0d1..0a4ce5c68f5a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid-expected.txt @@ -1,6 +1,11 @@ PASS e.style['continue'] = "none" should not set the property value +FAIL e.style['continue'] = "auto" should not set the property value assert_equals: expected "" but got "auto" PASS e.style['continue'] = "auto discard" should not set the property value PASS e.style['continue'] = "auto collapse" should not set the property value PASS e.style['continue'] = "auto -webkit-legacy" should not set the property value +PASS e.style['continue'] = "normal discard" should not set the property value +PASS e.style['continue'] = "normal collapse" should not set the property value +PASS e.style['continue'] = "normal -webkit-legacy" should not set the property value +PASS e.style['continue'] = "collapse -webkit-legacy" should not set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid.html index df09e0912beb..6c14c7751bb2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid.html @@ -4,7 +4,7 @@ CSS Overflow: parsing continue with invalid values - + @@ -12,10 +12,15 @@ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid-expected.txt index ba991634ee46..c987fbffd116 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid-expected.txt @@ -1,5 +1,5 @@ -PASS e.style['continue'] = "auto" should set the property value +FAIL e.style['continue'] = "normal" should set the property value assert_not_equals: property should be set got disallowed value "" PASS e.style['continue'] = "discard" should set the property value FAIL e.style['continue'] = "collapse" should set the property value assert_not_equals: property should be set got disallowed value "" FAIL e.style['continue'] = "-webkit-legacy" should set the property value assert_not_equals: property should be set got disallowed value "" diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid.html index 9d9f71e79ccf..571678e81739 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid.html @@ -4,14 +4,14 @@ CSS Overflow: parsing continue with valid values - + + + +
+
+
+
+
+
+
+
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid-expected.txt index 39fccc90cde1..969bd90e82e5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid-expected.txt @@ -4,6 +4,7 @@ PASS e.style['line-clamp'] = "-5" should not set the property value PASS e.style['line-clamp'] = "none 2" should not set the property value PASS e.style['line-clamp'] = "none no-ellipsis" should not set the property value PASS e.style['line-clamp'] = "3 none" should not set the property value +PASS e.style['line-clamp'] = "3 ellipsis auto" should not set the property value PASS e.style['line-clamp'] = "-webkit-legacy" should not set the property value PASS e.style['line-clamp'] = "0 -webkit-legacy" should not set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid.html index 36665f7d9bb6..6d3155714934 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid.html @@ -18,6 +18,7 @@ test_invalid_value("line-clamp", 'none 2'); test_invalid_value("line-clamp", 'none no-ellipsis'); test_invalid_value("line-clamp", '3 none'); +test_invalid_value("line-clamp", '3 ellipsis auto'); test_invalid_value("line-clamp", '-webkit-legacy'); test_invalid_value("line-clamp", '0 -webkit-legacy'); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid-expected.txt index 088a30672e3d..3c45aa81db79 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid-expected.txt @@ -3,12 +3,15 @@ PASS e.style['line-clamp'] = "none" should set the property value PASS e.style['line-clamp'] = "1" should set the property value PASS e.style['line-clamp'] = "6" should set the property value FAIL e.style['line-clamp'] = "auto" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['line-clamp'] = "ellipsis" should set the property value assert_not_equals: property should be set got disallowed value "" FAIL e.style['line-clamp'] = "\" etc., etc. \"" should set the property value assert_not_equals: property should be set got disallowed value "" FAIL e.style['line-clamp'] = "7 no-ellipsis" should set the property value assert_not_equals: property should be set got disallowed value "" -PASS e.style['line-clamp'] = "8 auto" should set the property value +FAIL e.style['line-clamp'] = "8 auto" should set the property value assert_equals: serialization should be canonical expected "8 auto" but got "8" +FAIL e.style['line-clamp'] = "8 ellipsis" should set the property value assert_not_equals: property should be set got disallowed value "" PASS e.style['line-clamp'] = "9 \" etc., etc. \"" should set the property value FAIL e.style['line-clamp'] = "no-ellipsis 10" should set the property value assert_not_equals: property should be set got disallowed value "" -PASS e.style['line-clamp'] = "auto 11" should set the property value +FAIL e.style['line-clamp'] = "auto 11" should set the property value assert_equals: serialization should be canonical expected "11 auto" but got "11" +FAIL e.style['line-clamp'] = "ellipsis 11" should set the property value assert_not_equals: property should be set got disallowed value "" PASS e.style['line-clamp'] = "\" etc., etc. \" 12" should set the property value FAIL e.style['line-clamp'] = "1 -webkit-legacy" should set the property value assert_not_equals: property should be set got disallowed value "" FAIL e.style['line-clamp'] = "auto -webkit-legacy" should set the property value assert_not_equals: property should be set got disallowed value "" diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid.html index 870049809b2d..e8d76921e795 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid.html @@ -15,22 +15,26 @@ test_valid_value("line-clamp", '1'); test_valid_value("line-clamp", '6'); - test_valid_value("line-clamp", 'auto'); -test_valid_value("line-clamp", '" etc., etc. "'); + +test_valid_value("line-clamp", 'ellipsis', 'auto'); +test_valid_value("line-clamp", '" etc., etc. "', 'auto " etc., etc. "'); test_valid_value("line-clamp", '7 no-ellipsis'); -test_valid_value("line-clamp", '8 auto', '8'); +test_valid_value("line-clamp", '8 auto'); +test_valid_value("line-clamp", '8 ellipsis', '8'); test_valid_value("line-clamp", '9 " etc., etc. "'); test_valid_value("line-clamp", 'no-ellipsis 10', '10 no-ellipsis'); -test_valid_value("line-clamp", 'auto 11', '11'); +test_valid_value("line-clamp", 'auto 11', '11 auto'); +test_valid_value("line-clamp", 'ellipsis 11', '11'); test_valid_value("line-clamp", '" etc., etc. " 12', '12 " etc., etc. "'); test_valid_value("line-clamp", '1 -webkit-legacy'); test_valid_value("line-clamp", 'auto -webkit-legacy'); -test_valid_value("line-clamp", 'no-ellipsis -webkit-legacy'); -test_valid_value("line-clamp", '3 auto -webkit-legacy', '3 -webkit-legacy'); +test_valid_value("line-clamp", 'no-ellipsis -webkit-legacy', + 'auto no-ellipsis -webkit-legacy'); +test_valid_value("line-clamp", '3 auto -webkit-legacy'); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid-expected.txt index e677f48aa9aa..a0590005992d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid-expected.txt @@ -1,8 +1,9 @@ -PASS e.style['max-lines'] = "auto" should not set the property value +FAIL e.style['max-lines'] = "none" should not set the property value assert_equals: expected "" but got "none" PASS e.style['max-lines'] = "0" should not set the property value PASS e.style['max-lines'] = "-5" should not set the property value PASS e.style['max-lines'] = "none none" should not set the property value +PASS e.style['max-lines'] = "auto auto" should not set the property value PASS e.style['max-lines'] = "1 none" should not set the property value PASS e.style['max-lines'] = "none 2" should not set the property value PASS e.style['max-lines'] = "3 4" should not set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid.html index 8b6da0dcb896..5fa973650be6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid.html @@ -12,11 +12,12 @@ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin-expected.txt index e94cb0959024..5054b44d3ab3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin-expected.txt @@ -23,4 +23,5 @@ PASS e.style['overflow-clip-margin'] = "border-box calc(0.5em - 100px)" should s PASS e.style['overflow-clip-margin'] = "border-box calc(0.5em - 100%)" should not set the property value PASS e.style['overflow-clip-margin'] = "margin-box" should not set the property value PASS e.style['overflow-clip-margin'] = "inset(10px)" should not set the property value +PASS e.style['overflow-clip-margin'] = "50px 50px" should not set the property value diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin.html index e1efee596ec0..2a1b988c5724 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-clip-margin.html @@ -40,6 +40,7 @@ test_invalid_value("overflow-clip-margin", 'margin-box'); test_invalid_value("overflow-clip-margin", 'inset(10px)'); +test_invalid_value("overflow-clip-margin", '50px 50px'); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed-expected.txt index fa7112933026..1dd040b531b4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed-expected.txt @@ -5,17 +5,17 @@ PASS Property overflow value 'clip' PASS Property overflow value 'scroll' PASS Property overflow value 'auto' PASS Property overflow value 'auto auto' -PASS Property overflow value 'auto clip' +FAIL Property overflow value 'auto clip' assert_equals: expected "auto clip" but got "auto hidden" PASS Property overflow value 'auto visible' -PASS Property overflow value 'clip auto' +FAIL Property overflow value 'clip auto' assert_equals: expected "clip auto" but got "hidden auto" PASS Property overflow value 'clip clip' -PASS Property overflow value 'clip hidden' -PASS Property overflow value 'clip scroll' +FAIL Property overflow value 'clip hidden' assert_equals: expected "clip hidden" but got "hidden" +FAIL Property overflow value 'clip scroll' assert_equals: expected "clip scroll" but got "hidden scroll" PASS Property overflow value 'clip visible' -PASS Property overflow value 'hidden clip' +FAIL Property overflow value 'hidden clip' assert_equals: expected "hidden clip" but got "hidden" PASS Property overflow value 'hidden visible' PASS Property overflow value 'scroll auto' -PASS Property overflow value 'scroll clip' +FAIL Property overflow value 'scroll clip' assert_equals: expected "scroll clip" but got "scroll hidden" PASS Property overflow value 'scroll visible' PASS Property overflow value 'visible auto' PASS Property overflow value 'visible hidden' diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed.html index 563d1b31d259..a7d6e2fa3133 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed.html @@ -4,7 +4,7 @@ CSS Overflow: getComputedStyle().overflow - + @@ -19,17 +19,17 @@ test_computed_value("overflow", 'auto'); test_computed_value("overflow", 'auto auto', 'auto'); -test_computed_value("overflow", 'auto clip', 'auto hidden'); +test_computed_value("overflow", 'auto clip'); test_computed_value("overflow", 'auto visible', 'auto'); -test_computed_value("overflow", 'clip auto', 'hidden auto'); +test_computed_value("overflow", 'clip auto'); test_computed_value("overflow", 'clip clip', 'clip'); -test_computed_value("overflow", 'clip hidden', 'hidden'); -test_computed_value("overflow", 'clip scroll', 'hidden scroll') -test_computed_value("overflow", 'clip visible', 'clip visible') -test_computed_value("overflow", 'hidden clip', 'hidden'); +test_computed_value("overflow", 'clip hidden'); +test_computed_value("overflow", 'clip scroll'); +test_computed_value("overflow", 'clip visible', 'clip visible'); +test_computed_value("overflow", 'hidden clip'); test_computed_value("overflow", 'hidden visible', 'hidden auto'); test_computed_value("overflow", 'scroll auto'); -test_computed_value("overflow", 'scroll clip', 'scroll hidden'); +test_computed_value("overflow", 'scroll clip'); test_computed_value("overflow", 'scroll visible', 'scroll auto'); test_computed_value("overflow", 'visible auto', 'auto'); test_computed_value("overflow", 'visible hidden', 'auto hidden'); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed-expected.txt new file mode 100644 index 000000000000..dccad9439cd0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed-expected.txt @@ -0,0 +1,10 @@ + +FAIL Property scroll-axis-lock value 'initial' assert_true: scroll-axis-lock doesn't seem to be supported in the computed style expected true got false +FAIL Property scroll-axis-lock value 'inherit' assert_true: scroll-axis-lock doesn't seem to be supported in the computed style expected true got false +FAIL Property scroll-axis-lock value 'unset' assert_true: scroll-axis-lock doesn't seem to be supported in the computed style expected true got false +FAIL Property scroll-axis-lock value 'revert' assert_true: scroll-axis-lock doesn't seem to be supported in the computed style expected true got false +FAIL Property scroll-axis-lock value 'auto' assert_true: scroll-axis-lock doesn't seem to be supported in the computed style expected true got false +FAIL Property scroll-axis-lock value 'none' assert_true: scroll-axis-lock doesn't seem to be supported in the computed style expected true got false +FAIL The scroll-axis-lock property shows up in CSSStyleDeclaration enumeration assert_not_equals: got disallowed value -1 +FAIL The scroll-axis-lock property shows up in CSSStyleDeclaration.cssText assert_not_equals: got disallowed value -1 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed.html new file mode 100644 index 000000000000..6b14cd9165d0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed.html @@ -0,0 +1,27 @@ + + +CSS Overflow: parsing scroll-axis-lock property computed values + + + + +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid-expected.txt new file mode 100644 index 000000000000..45a60854380f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid-expected.txt @@ -0,0 +1,9 @@ + +PASS e.style['scroll-axis-lock'] = "10" should not set the property value +PASS e.style['scroll-axis-lock'] = "true" should not set the property value +PASS e.style['scroll-axis-lock'] = "default" should not set the property value +PASS e.style['scroll-axis-lock'] = "all" should not set the property value +PASS e.style['scroll-axis-lock'] = "auto, none" should not set the property value +PASS e.style['scroll-axis-lock'] = "always" should not set the property value +PASS e.style['scroll-axis-lock'] = "both" should not set the property value + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid.html new file mode 100644 index 000000000000..00e5b920a8b4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid.html @@ -0,0 +1,17 @@ + + +CSS Overflow: parsing scroll-axis-lock property invalid values + + + + +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid-expected.txt new file mode 100644 index 000000000000..4a367297066c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid-expected.txt @@ -0,0 +1,8 @@ + +FAIL e.style['scroll-axis-lock'] = "initial" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['scroll-axis-lock'] = "inherit" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['scroll-axis-lock'] = "unset" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['scroll-axis-lock'] = "revert" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['scroll-axis-lock'] = "auto" should set the property value assert_not_equals: property should be set got disallowed value "" +FAIL e.style['scroll-axis-lock'] = "none" should set the property value assert_not_equals: property should be set got disallowed value "" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid.html new file mode 100644 index 000000000000..0a67bba52b2b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid.html @@ -0,0 +1,17 @@ + + +CSS Overflow: parsing scroll-axis-lock property valid values + + + + +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-markers-computed.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-markers-computed.html index 882ed83fe723..b9a68c20f094 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-markers-computed.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-markers-computed.html @@ -18,8 +18,8 @@ test_computed_value('scroll-marker-group', 'revert', 'none'); test_computed_value('scroll-marker-group', 'none'); - test_computed_value('scroll-marker-group', 'before'); - test_computed_value('scroll-marker-group', 'after'); + test_computed_value('scroll-marker-group', 'before', 'before links'); + test_computed_value('scroll-marker-group', 'after', 'after links'); test(() => { let style = getComputedStyle(document.getElementById('target')); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/w3c-import.log index 60907de1ad53..1d6008ff03a8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/w3c-import.log @@ -10,8 +10,6 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: None -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/WEB_FEATURES.yml @@ -19,6 +17,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/block-ellipsis-valid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-invalid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/continue-valid.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/getComputedStyle-scroll-button.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-invalid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/line-clamp-valid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/max-lines-invalid.html @@ -28,6 +27,9 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-computed.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-invalid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/overflow-valid.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-computed.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-invalid.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-axis-lock-valid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-buttons-invalid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-buttons-valid.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/parsing/scroll-markers-computed.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/before-after-pseudo-element-scrolling-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/before-after-pseudo-element-scrolling-ref.html new file mode 100644 index 000000000000..b4c8db95f3e6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/before-after-pseudo-element-scrolling-ref.html @@ -0,0 +1,6 @@ + + +CSS Reftest Reference + +

Test passes if there is a filled green square and no red.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-001-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-001-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-001-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-001-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-002-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-002-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-002-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-002-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-005-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-005-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-005-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-005-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-006-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-006-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-006-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-006-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-008-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-008-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-008-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-008-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-012-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-012-ref.html similarity index 95% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-012-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-012-ref.html index 700f9c896fe9..707f93d79c49 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-012-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-012-ref.html @@ -9,7 +9,7 @@ color: green; } span { - color: white; + color: white; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-013-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-013-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-013-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-013-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-016-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-016-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-016-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-016-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-021-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-021-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-021-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-021-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-022-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-022-ref.html similarity index 93% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-022-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-022-ref.html index db7d08f45045..31501b868471 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-022-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-022-ref.html @@ -9,7 +9,7 @@ color: green; } span { - color: transparent; + color: transparent; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-027-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-027-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-027-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-027-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-028-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-028-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-028-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-028-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-029-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-029-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-029-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-029-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-030-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-030-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-030-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-030-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-change-color-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-change-color-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-change-color-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-change-color-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-editable-div-with-caret-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-editable-div-with-caret-ref.html new file mode 100644 index 000000000000..97e1ffdca126 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-editable-div-with-caret-ref.html @@ -0,0 +1,29 @@ + + +Reference for text-overflow ellipsis in editable div with caret selection + + +
This is a very long text that should be clipped
+ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-ellipsis-indent-001-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-indent-001-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-ellipsis-indent-001-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-indent-001-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-ellipsis-multiline-001-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-multiline-001-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-ellipsis-multiline-001-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-multiline-001-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-textarea-with-caret-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-textarea-with-caret-ref.html new file mode 100644 index 000000000000..77bfc030f70f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-textarea-with-caret-ref.html @@ -0,0 +1,25 @@ + + +Reference for text-overflow ellipsis in textarea with caret selection + + + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-001-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-001-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-001-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-001-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-002-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-002-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-002-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-002-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-003-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-003-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-003-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-003-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-004-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-004-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-004-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-004-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-005-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-005-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-005-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-005-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-006-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-006-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-006-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-006-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-007-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-007-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-007-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-007-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-008-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-008-ref.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/reference/text-overflow-string-008-ref.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-008-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-009-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-009-ref.html new file mode 100644 index 000000000000..3a3b90047ef4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-009-ref.html @@ -0,0 +1,25 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an 안녕 안녕 after a black rectangle below.

+
+ Test안녕 안녕 +
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-010-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-010-ref.html new file mode 100644 index 000000000000..2a76f91ed132 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-010-ref.html @@ -0,0 +1,25 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an 안녕 你好 after a black rectangle below.

+
+ Test안녕 你好 +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-011-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-011-ref.html new file mode 100644 index 000000000000..0e89803a16ea --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-011-ref.html @@ -0,0 +1,21 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an Hi ワールド after a black rectangle below.

+
+ TestHi ワールド +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-012-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-012-ref.html new file mode 100644 index 000000000000..6134b3efd9f6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-012-ref.html @@ -0,0 +1,29 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an 안녕 🟢 after a black rectangle below.

+
+ Test안녕 🟢 +
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-013-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-013-ref.html new file mode 100644 index 000000000000..cc57fa1e6441 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-013-ref.html @@ -0,0 +1,25 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an 你好 🟢 after a black rectangle below.

+
+ Test你好 🟢 +
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-014-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-014-ref.html new file mode 100644 index 000000000000..2d24573aaa9e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-014-ref.html @@ -0,0 +1,25 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an שלום 你好 after a black rectangle below.

+
+ Testשלום 你好 +
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-015-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-015-ref.html new file mode 100644 index 000000000000..3a70eecf878b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-015-ref.html @@ -0,0 +1,29 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an יְוֹם 낮 after a black rectangle below.

+
+ Testיְוֹם 낮 +
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-016-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-016-ref.html new file mode 100644 index 000000000000..1076f4939711 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-016-ref.html @@ -0,0 +1,21 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an Hi Hi after a black rectangle below.

+
+ TestHi Hi +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-024-ref.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-024-ref.tentative.html new file mode 100644 index 000000000000..97a5a87438dd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-024-ref.tentative.html @@ -0,0 +1,21 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an こんにちは after a black rectangle below.

+
+ Testこんにちは +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-025-ref.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-025-ref.tentative.html new file mode 100644 index 000000000000..ae378c472c19 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-025-ref.tentative.html @@ -0,0 +1,21 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an 你好你好 after a black rectangle below.

+
+ Test你好你好 +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-026-ref.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-026-ref.tentative.html new file mode 100644 index 000000000000..594a188b001c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-026-ref.tentative.html @@ -0,0 +1,21 @@ + + +CSS Basic User Interface Reference File + + + + +

Test passes if there is an FULL after a black rectangle below.

+
+ TestFULL +
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/w3c-import.log index f61c0946ce17..8b242c970423 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/w3c-import.log @@ -10,10 +10,9 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: None -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/before-after-pseudo-element-scrolling-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/input-scrollable-region-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/overflow-body-no-propagation-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/overflow-body-propagation-ref.html @@ -21,12 +20,50 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/overflow-inline-block-with-opacity-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/overflow-recalc-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/ref-if-there-is-no-red.xht +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-001-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-002-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-005-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-006-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-008-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-012-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-013-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-016-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-021-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-022-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-027-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-028-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-029-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-030-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-change-color-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-002-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-editable-div-with-caret-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-indent-001-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-multiline-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-rtl-001-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-textarea-with-caret-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-vertical-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-ellipsis-vertical-rtl-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-scroll-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-scroll-rtl-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-scroll-vertical-lr-001-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-scroll-vertical-lr-rtl-001-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-001-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-002-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-003-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-004-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-005-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-006-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-007-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-008-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-009-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-010-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-011-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-012-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-013-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-014-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-015-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-016-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-024-ref.tentative.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-025-ref.tentative.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/reference/text-overflow-string-026-ref.tentative.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock-expected.txt new file mode 100644 index 000000000000..224d30a3ae00 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock-expected.txt @@ -0,0 +1,6 @@ + +Harness Error (TIMEOUT), message = null + +TIMEOUT scroll-axis-lock: none allows free diagonal touch scroll even with steep angle (no railing) Test timed out +NOTRUN scroll-axis-lock: none allows free diagonal wheel scroll even with steep angle (no railing) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock.html new file mode 100644 index 000000000000..583ed053f332 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-axis-lock.html @@ -0,0 +1,125 @@ + +CSS Scroll Snap: scroll-axis-lock + + + + + + + + + +
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none-expected.txt new file mode 100644 index 000000000000..cff8a4a2d85d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none-expected.txt @@ -0,0 +1,4 @@ +Item + +PASS No crash when scroll-marker-group is display: none during focus navigation + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none.html new file mode 100644 index 000000000000..f59139223faa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-display-none.html @@ -0,0 +1,47 @@ + +CSS Overflow: scroll markers crash with display:none + + + + + + +
+
Item
+
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-expected.txt new file mode 100644 index 000000000000..8d7713226b0d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-expected.txt @@ -0,0 +1,3 @@ + +FAIL CSS Overflow Test: ::scroll-marker-group supports hover assert_equals: ::scroll-marker-group is unhovered expected "rgb(255, 0, 0)" but got "" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker-expected.txt new file mode 100644 index 000000000000..96e33d3a6cb9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker-expected.txt @@ -0,0 +1,3 @@ + +FAIL CSS Overflow Test: ::scroll-marker-group matches hover when inner ::scroll-marker is hovered assert_equals: ::scroll-marker-group is unhovered initially expected "rgb(255, 0, 0)" but got "" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker.html new file mode 100644 index 000000000000..7ad3ee10f315 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover-from-marker.html @@ -0,0 +1,64 @@ + + +CSS Overflow Test: ::scroll-marker-group matches hover when inner ::scroll-marker is hovered + + + + + + + +
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover.html new file mode 100644 index 000000000000..9b8097105bbc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-marker-group-hover.html @@ -0,0 +1,42 @@ + + +CSS Overflow Test: ::scroll-marker-group supports hover + + + + + + + +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yaml b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yaml deleted file mode 100644 index b3862f22ed98..000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yaml +++ /dev/null @@ -1,18 +0,0 @@ -features: -- name: scroll-markers - files: - - column-scroll-marker* - - html-scroll-marker* - - inline-with-scroll-marker* - - nested-scroll-markers* - - root-scroll-marker* - - scroll-marker* - - target-current-scroll-marker-update.html - - targeted-column-scroll-marker* - - targeted-scroll-marker* - - chrome-421199213-crash.html - - targeted-column-scroll-marker-selection-* -- name: scroll-buttons - files: - - root-scroll-button* - - scroll-button* diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yml b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yml new file mode 100644 index 000000000000..70efb93af80b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yml @@ -0,0 +1,15 @@ +rules: +- column-scroll-marker*: [scroll-markers] +- html-scroll-marker*: [scroll-marker-targets] +- inline-with-scroll-marker*: [scroll-markers] +- nested-scroll-markers*: [scroll-markers] +- root-scroll-marker*: [scroll-markers] +- scroll-marker-target-before-after.html: [scroll-marker-targets] +- scroll-marker*: [scroll-markers] +- target-current-scroll-marker-update.html: [scroll-markers] +- targeted-column-scroll-marker*: [scroll-markers, scroll-markers] +- targeted-scroll-marker*: [scroll-markers] +- chrome-421199213-crash.html: [scroll-markers] +- root-scroll-button*: [scroll-buttons] +- scroll-button*: [scroll-buttons] +- scroll-target-group*: [scroll-target-group] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/resources/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/resources/w3c-import.log index dea262857ba6..3a0bc3ad17e9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/resources/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/resources/w3c-import.log @@ -10,8 +10,6 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: None -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/resources/root-scroll-marker-activation-iframe.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-display-none.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-display-none.html index 8b3068be6dad..35e87c5556c1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-display-none.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-display-none.html @@ -51,4 +51,22 @@ .send(); assert_true(true); }); + + promise_test(async t => { + // Restore the scroller visibility hidden by the previous test. + scroller.className = ""; + document.documentElement.offsetTop; + + // Removing the scroller inside a bubble-phase listener. So that by the + // time a handler is called the pseudo-element is already disconnected. + scroller.addEventListener('click', () => scroller.remove(), { once: true }); + + await new test_driver.Actions() + .pointerMove(15, 15) + .pointerDown() + .pointerUp() + .send(); + + assert_true(true, "no crash when container removed during event bubbling"); + }, "scroll-button click removing container during bubbling does not crash"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-expected.html new file mode 100644 index 000000000000..65707c53527c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-expected.html @@ -0,0 +1,15 @@ + +CSS Overflow Test: ::scroll-button positioning works after position type transitions + +

You should see a green square below.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-ref.html new file mode 100644 index 000000000000..65707c53527c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-ref.html @@ -0,0 +1,15 @@ + +CSS Overflow Test: ::scroll-button positioning works after position type transitions + +

You should see a green square below.

+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position.html new file mode 100644 index 000000000000..c9ebb585e3fc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position.html @@ -0,0 +1,34 @@ + +CSS Overflow Test: ::scroll-button positioning works after position type transitions + + + +

You should see a green square below.

+
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-activation-retains-focus.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-activation-retains-focus.html index cf38ed354c73..054a87011993 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-activation-retains-focus.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-activation-retains-focus.html @@ -1,6 +1,6 @@ -CSS Test: ::scroll-marker retains focus on ::scroll-marker +CSS Test: ::scroll-marker activation focus behavior in links and tabs modes @@ -51,12 +51,25 @@
\ No newline at end of file + assert_equals(getComputedStyle(target, "::scroll-marker").backgroundColor, "rgb(0, 0, 255)", "::scroll-marker activation retains focus in tabs mode"); + }, "::scroll-marker activation focus behavior in links and tabs modes"); + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-double-activation.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-double-activation.html index ee621980c286..d6d14e4d90f8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-double-activation.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-double-activation.html @@ -1,6 +1,6 @@ -CSS Values Test: ::scroll-marker double activation scrolls into view twice +CSS Values Test: ::scroll-marker double activation scrolls into view twice in links and tabs modes @@ -51,13 +51,36 @@
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-focus-within.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-focus-within.html index 7a094b2bfd45..4cd3945bd21a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-focus-within.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-focus-within.html @@ -1,6 +1,6 @@ -CSS Overflow Test: focused ::scroll-marker sets :focus-within on its ::scroll-marker-group, scroller and all the ancestors +CSS Overflow Test: focused ::scroll-marker sets :focus-within on its ::scroll-marker-group, scroller and all the ancestors in links and tabs modes @@ -65,24 +65,31 @@
\ No newline at end of file + assert_equals(getComputedStyle(scroller, "::scroll-marker-group").backgroundColor, "rgb(0, 128, 0)", "focused ::scroll-marker sets :focus-within on its ::scroll-marker-group in tabs mode"); + assert_equals(getComputedStyle(scroller).backgroundColor, "rgb(0, 128, 0)", "focused ::scroll-marker sets :focus-within on its scroller in tabs mode"); + assert_equals(getComputedStyle(target, "::scroll-marker").backgroundColor, "rgb(0, 128, 0)", "focused ::scroll-marker sets :focus-within on itself in tabs mode"); + assert_equals(getComputedStyle(target).backgroundColor, "rgb(0, 128, 0)", "focused ::scroll-marker doesn't set :focus-within on its originating element in tabs mode"); + }, "::scroll-marker sets :focus-within on its ancestors in tabs mode, but not in links mode"); + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-inert-003.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-inert-003.html new file mode 100644 index 000000000000..362f715f6a45 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-inert-003.html @@ -0,0 +1,49 @@ + +CSS Overflow Test: ::scroll-marker-group inertness applied to display:contents ::scroll-marker + + + + + + + + + +
+
+
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-navigation-cycles.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-navigation-cycles.html index d41b720ed925..5dfe09053c90 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-navigation-cycles.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-navigation-cycles.html @@ -14,54 +14,206 @@ } #scroller { + background: white; overflow: auto; - width: 100px; - height: 100px; - scroll-marker-group: before; + width: 600px; + height: 300px; + /* use tabs mode so activated markers receive focus and can handle + arrow-key events directly */ + scroll-marker-group: before tabs; white-space: nowrap; } #scroller::scroll-marker-group { display: flex; - height: 10px; - width: 30px; + height: 20px; + width: 300px; } #scroller div { background: blue; display: inline-block; - height: 100px; - width: 100px; + height: 280px; + width: 600px; } #scroller div::scroll-marker { content: ""; background: blue; - width: 10px; - height: 10px; + display: inline-block; + width: 30px; + height: 20px; + opacity: 1; } #scroller div::scroll-marker:target-current { background: green; } + + #scroller div::scroll-marker:focus { + opacity: 0.5; + }
-
+
\ No newline at end of file + await waitForRenderingUpdate(); + } + + async function resetScrollerStyles() { + scroller.style.direction = 'ltr'; + scroller.style.writingMode = 'horizontal-tb'; + scroller.scrollTo({ left: 0, top: 0, behavior: 'instant' }); + await waitForRenderingUpdate(); + } + + async function focusAndActivateFirstMarker() { + await resetScrollerStyles(); + await clickAt(15, 15); + assert_equals(getActiveMarker(), 'first', 'click activates the first ::scroll-marker'); + assert_true(markerIsFocused(), 'clicking the first ::scroll-marker focuses it'); + } + + promise_test(async t => { + // Basic LTR navigation sanity check. + // In horizontal-tb: + // Block axis is Top to Bottom. + // Inline axis is Left to Right. + await focusAndActivateFirstMarker(); + await sendKey(kArrowRight); + assert_equals(getActiveMarker(), 'middle', 'right arrow moves forward in horizontal-tb ltr'); + + await focusAndActivateFirstMarker(); + await sendKey(kArrowLeft); + assert_equals(getActiveMarker(), 'last', 'left arrow wraps backward in horizontal-tb ltr'); + + await focusAndActivateFirstMarker(); + await sendKey(kArrowDown); + assert_equals(getActiveMarker(), 'middle', 'down arrow moves forward in horizontal-tb ltr'); + + await focusAndActivateFirstMarker(); + await sendKey(kArrowUp); + assert_equals(getActiveMarker(), 'last', 'up arrow wraps backward in horizontal-tb ltr'); + + // Check that RTL flips the behavior of the horizontal arrows. + await focusAndActivateFirstMarker(); + scroller.style.direction = 'rtl'; + await waitForRenderingUpdate(); + assert_true(markerIsFocused(), 'changing to RTL keeps focus on the first ::scroll-marker'); + await sendKey(kArrowRight); + assert_equals(getActiveMarker(), 'last', 'in RTL, right arrow wraps backward'); + + // In RTL, left arrow acts like right arrow in LTR, so it moves forward from first to middle + await focusAndActivateFirstMarker(); + scroller.style.direction = 'rtl'; + await waitForRenderingUpdate(); + await sendKey(kArrowLeft); + assert_equals(getActiveMarker(), 'middle', 'in RTL, left arrow moves forward'); + }, 'Arrow keys in horizontal-tb'); + + promise_test(async t => { + // In vertical-lr: + // Block axis is Left to Right. + // Inline axis is Top to Bottom. + await focusAndActivateFirstMarker(); + scroller.style.writingMode = 'vertical-lr'; + await waitForRenderingUpdate(); + + await sendKey(kArrowDown); + assert_equals(getActiveMarker(), 'middle', 'down arrow moves forward in vertical-lr'); + await sendKey(kArrowUp); + assert_equals(getActiveMarker(), 'first', 'up arrow moves backward in vertical-lr'); + await sendKey(kArrowRight); + assert_equals(getActiveMarker(), 'middle', 'right arrow moves forward in vertical-lr'); + await sendKey(kArrowLeft); + assert_equals(getActiveMarker(), 'first', 'left arrow moves backward in vertical-lr'); + }, 'Arrow keys in vertical-lr'); + + promise_test(async t => { + // In vertical-rl: + // Block axis is Right to Left. + // Inline axis is Top to Bottom. + await focusAndActivateFirstMarker(); + scroller.style.writingMode = 'vertical-rl'; + await waitForRenderingUpdate(); + + await sendKey(kArrowDown); + assert_equals(getActiveMarker(), 'middle', 'down arrow moves forward in vertical-rl'); + await sendKey(kArrowUp); + assert_equals(getActiveMarker(), 'first', 'up arrow moves backward in vertical-rl'); + await sendKey(kArrowRight); + assert_equals(getActiveMarker(), 'last', 'right arrow wraps backward in vertical-rl'); + await sendKey(kArrowLeft); + assert_equals(getActiveMarker(), 'first', 'left arrow moves forward in vertical-rl'); + }, 'Arrow keys in vertical-rl'); + + promise_test(async t => { + // In vertical-rl with rtl direction: + // Block axis is Right to Left. + // Inline axis is Bottom to Top. + await focusAndActivateFirstMarker(); + scroller.style.writingMode = 'vertical-rl'; + scroller.style.direction = 'rtl'; + await waitForRenderingUpdate(); + + await sendKey(kArrowDown); + assert_equals(getActiveMarker(), 'last', 'down arrow wraps backward in vertical-rl rtl'); + await sendKey(kArrowUp); + assert_equals(getActiveMarker(), 'first', 'up arrow moves forward in vertical-rl rtl'); + await sendKey(kArrowLeft); + assert_equals(getActiveMarker(), 'middle', 'left arrow moves forward in vertical-rl rtl'); + await sendKey(kArrowRight); + assert_equals(getActiveMarker(), 'first', 'right arrow moves backward in vertical-rl rtl'); + }, 'Arrow keys in vertical-rl rtl'); + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-selection-picks-closest.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-selection-picks-closest.html index e0e46ba1d425..3f7ac296e785 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-selection-picks-closest.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-selection-picks-closest.html @@ -8,7 +8,7 @@ - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-active-element.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-active-element.html index 0b2d68b5773f..89d6f64de23d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-active-element.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-active-element.html @@ -1,6 +1,6 @@ -CSS Test: document.activeElement for ::scroll-marker is scroller +CSS Test: document.activeElement for ::scroll-marker in links and tabs modes @@ -56,12 +56,25 @@
\ No newline at end of file + assert_equals(document.activeElement, scroller, "document.activeElement for ::scroll-marker is scroller in tabs mode"); + }, "document.activeElement for ::scroll-marker is document.body in links mode, and scroller in tabs mode"); + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-on-scrolling.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-on-scrolling.html index 8e5851a49735..8278771a237e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-on-scrolling.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-on-scrolling.html @@ -1,6 +1,6 @@ -CSS Test: scroll tracking for ::scroll-marker +CSS Test: scroll tracking for ::scroll-marker in links and tabs modes @@ -62,10 +62,34 @@ " but was " + pseudoOpacity.toString()); } promise_test(async () => { + // 1. Links mode (default) scroller.scrollTop = 150; assertPseudoElementProperty(target, "::scroll-marker", "1"); - }, "active ::scroll-marker doesn't have focus on scroll if previous ::scroll-marker didn't have it"); + }, "active ::scroll-marker doesn't have focus on scroll if previous ::scroll-marker didn't have it in links mode"); + promise_test(async () => { + // 1. Links mode (default) + /* Click the first ::scroll-marker to attempt to give it focus. */ + let actions_promise = new test_driver.Actions() + .pointerMove(7, 7) + .pointerDown() + .pointerUp() + .pointerDown() + .pointerUp() + .send(); + await actions_promise; + await waitForAnimationFrames(2); + scroller.scrollTop = 150; + await waitForAnimationFrames(2); + assertPseudoElementProperty(target, "::scroll-marker", "1"); + }, "active ::scroll-marker does not save focus on scroll in links mode because click does not focus the marker"); + + promise_test(async () => { + // 2. Tabs mode + document.activeElement.blur(); + scroller.style.setProperty("scroll-marker-group", "before tabs"); + await waitForAnimationFrames(2); + /* Click the first ::scroll-marker to give it focus. */ let actions_promise = new test_driver.Actions() .pointerMove(7, 7) @@ -79,5 +103,5 @@ scroller.scrollTop = 150; await waitForAnimationFrames(2); assertPseudoElementProperty(target, "::scroll-marker", "0.5"); - }, "active ::scroll-marker saves focus on scroll if previous ::scroll-marker had it"); + }, "active ::scroll-marker saves focus on scroll if previous ::scroll-marker had it in tabs mode"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-nested-scrollers.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-nested-scrollers.html new file mode 100644 index 000000000000..35f00da9bb3a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-nested-scrollers.html @@ -0,0 +1,87 @@ + + +CSS Overflow Test: scroll tracking for ::scroll-marker in nested scrollers + + + + + +
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-014.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-014.html new file mode 100644 index 000000000000..45cbd057c2b5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-014.html @@ -0,0 +1,76 @@ + +CSS Overflow: scroll-target-group: auto with container-type: inline-size + + + + +
+
+
+
Section 1
+
Section 2
+
Section 3
+
+
+ \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-iframe.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-iframe.html new file mode 100644 index 000000000000..049dec61c30a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-iframe.html @@ -0,0 +1,81 @@ + +CSS Overflow: scroll-target-group: auto with iframe sibling + + + + + +
+
Section 1
+ +
Section 2
+
Section 3
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/support/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/support/w3c-import.log index 3395d31b8cf9..1dc4bec0bf95 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/support/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/support/w3c-import.log @@ -10,8 +10,6 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: None -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/support/scroll-marker-support.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-expected.html index e4dbe28a85a9..033f34d27002 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-expected.html @@ -20,7 +20,6 @@ overflow-x: scroll; scroll-snap-type: x mandatory; list-style-type: none; - scroll-behavior: smooth; border: solid 2px grey; padding-top: 10%; text-align: center; @@ -38,13 +37,15 @@ margin: 3px; background-color: red; } - /* item 2 is child 3 */ + /* item2 is child 3. item2.scrollIntoView aligns to its center. */ + & > :nth-child(3) { + scroll-snap-align: center; + } & > :nth-child(3)::scroll-marker { background-color: green; } &>.item { - scroll-snap-align: center; height: 80%; width: 158px; border: 1px solid; diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-ref.html index e4dbe28a85a9..033f34d27002 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001-ref.html @@ -20,7 +20,6 @@ overflow-x: scroll; scroll-snap-type: x mandatory; list-style-type: none; - scroll-behavior: smooth; border: solid 2px grey; padding-top: 10%; text-align: center; @@ -38,13 +37,15 @@ margin: 3px; background-color: red; } - /* item 2 is child 3 */ + /* item2 is child 3. item2.scrollIntoView aligns to its center. */ + & > :nth-child(3) { + scroll-snap-align: center; + } & > :nth-child(3)::scroll-marker { background-color: green; } &>.item { - scroll-snap-align: center; height: 80%; width: 158px; border: 1px solid; diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001.html index a9b79ae1d097..46e7f839a8c7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-001.html @@ -24,7 +24,6 @@ overflow-x: scroll; scroll-snap-type: x mandatory; list-style-type: none; - scroll-behavior: smooth; border: solid 2px grey; padding-top: 10%; text-align: center; diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-expected.html index 6f28d562d9c9..b82ec1b35a24 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-expected.html @@ -51,9 +51,8 @@ display: inline-block; } - /* Item 3 is child 4. Starting from scrollLeft=0, calling - box4.scrollIntoView ends up in a position that aligns box 3. */ - & > :nth-child(4){ + /* item4 is child 5. item4.scrollIntoView aligns to its center. */ + & > :nth-child(5){ scroll-snap-align: center; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-ref.html index 6f28d562d9c9..b82ec1b35a24 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002-ref.html @@ -51,9 +51,8 @@ display: inline-block; } - /* Item 3 is child 4. Starting from scrollLeft=0, calling - box4.scrollIntoView ends up in a position that aligns box 3. */ - & > :nth-child(4){ + /* item4 is child 5. item4.scrollIntoView aligns to its center. */ + & > :nth-child(5){ scroll-snap-align: center; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002.html index 08c3a302a549..8acb21284ff5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-002.html @@ -24,7 +24,6 @@ overflow-x: scroll; scroll-snap-type: x mandatory; list-style-type: none; - scroll-behavior: smooth; border: solid 2px grey; padding-top: 10%; text-align: center; diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-expected.html index 4ead4f45813a..1c2310112f5f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-expected.html @@ -15,7 +15,7 @@ } .carousel { - width: 800px; + width: 500px; height: 200px; overflow-x: scroll; scroll-snap-type: x mandatory; @@ -68,15 +68,14 @@ &>.item { scroll-snap-align: none; height: 80%; - width: 158px; + width: 80px; border: 1px solid; place-content: center; display: inline-block; } - /* Make only item 16 (index 15) a snap target so we are scrolled all the - way to the right edge */ - & > :nth-child(14){ + /* item14 is child 15. item14.scrollIntoView() aligns to its center. */ + & > :nth-child(15){ scroll-snap-align: center; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-ref.html index 4ead4f45813a..1c2310112f5f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003-ref.html @@ -15,7 +15,7 @@ } .carousel { - width: 800px; + width: 500px; height: 200px; overflow-x: scroll; scroll-snap-type: x mandatory; @@ -68,15 +68,14 @@ &>.item { scroll-snap-align: none; height: 80%; - width: 158px; + width: 80px; border: 1px solid; place-content: center; display: inline-block; } - /* Make only item 16 (index 15) a snap target so we are scrolled all the - way to the right edge */ - & > :nth-child(14){ + /* item14 is child 15. item14.scrollIntoView() aligns to its center. */ + & > :nth-child(15){ scroll-snap-align: center; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003.html index 4f8f68646ded..845c5a8e3431 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-003.html @@ -19,12 +19,11 @@ } .carousel { - width: 800px; + width: 500px; height: 200px; overflow-x: scroll; scroll-snap-type: x mandatory; list-style-type: none; - scroll-behavior: smooth; border: solid 2px grey; padding-top: 10%; text-align: center; @@ -58,7 +57,7 @@ &>.item { scroll-snap-align: center; height: 80%; - width: 158px; + width: 80px; border: 1px solid; place-content: center; display: inline-block; diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-expected.html index 6e42518107cf..69f872308731 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-expected.html @@ -77,10 +77,8 @@ display: inline-block; } - /* The test calls scrollIntoView on item 13 (child 14). Make only item 11 - (index 12) a snap target as it is what is aligned when scrollintoView - is called on item 13. */ - & > :nth-child(12){ + /* item13 is child 14. item13.scrollIntoView aligns to its center. */ + & > :nth-child(14){ scroll-snap-align: center; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-ref.html index 6e42518107cf..69f872308731 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004-ref.html @@ -77,10 +77,8 @@ display: inline-block; } - /* The test calls scrollIntoView on item 13 (child 14). Make only item 11 - (index 12) a snap target as it is what is aligned when scrollintoView - is called on item 13. */ - & > :nth-child(12){ + /* item13 is child 14. item13.scrollIntoView aligns to its center. */ + & > :nth-child(14){ scroll-snap-align: center; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004.html index 60ed33c73b67..acc8f2ed011c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/targeted-column-scroll-marker-selection-004.html @@ -24,7 +24,6 @@ overflow-x: scroll; scroll-snap-type: x mandatory; list-style-type: none; - scroll-behavior: smooth; border: solid 2px grey; padding-top: 10%; text-align: center; diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/w3c-import.log index fbb9ae657c8d..71db5a56ff8f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/w3c-import.log @@ -10,11 +10,9 @@ Do NOT modify or remove this file. ------------------------------------------------------------------------ Properties requiring vendor prefixes: None -Property values requiring vendor prefixes: -None ------------------------------------------------------------------------ List of files: -/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yaml +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/WEB_FEATURES.yml /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/chrome-421199213-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/column-scroll-marker-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/column-scroll-marker-001-ref.html @@ -88,6 +86,9 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-on-object-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-on-object-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-on-object.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-reattachment-position.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-button-universal-before.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-buttons-001-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-buttons-001-ref.html @@ -261,6 +262,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-in-display-none-column-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-inert-001.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-inert-002.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-inert-003.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-multiple-activation.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-navigation-cycles.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-marker-next-focus.html @@ -282,6 +284,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-focus-on-scrolling.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-inside-canvas-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-inside-select-crash.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-nested-scrollers.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-resize-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-under-content-visibility-auto-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-markers-under-content-visibility-auto-ref.html @@ -319,6 +322,8 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-012-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-012.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-013.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-014.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-iframe.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-inline-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-inline-targets-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scroll-markers/scroll-target-group-inline-targets-ref.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scrollbar-gutter-zero-width-crash.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scrollbar-gutter-zero-width-crash.html new file mode 100644 index 000000000000..7f1fce431d9a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/scrollbar-gutter-zero-width-crash.html @@ -0,0 +1,16 @@ + + +CSS Overflow: zero-width scroller with scrollbar-gutter should not crash + + + +
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-expected.html new file mode 100644 index 000000000000..14f3b8663a55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-expected.html @@ -0,0 +1,25 @@ + + +Single-axis scroll containers: Clipped X Rendering RTL (Reference) + + +
+
RTL gradient and text
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-ref.html new file mode 100644 index 000000000000..14f3b8663a55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl-ref.html @@ -0,0 +1,25 @@ + + +Single-axis scroll containers: Clipped X Rendering RTL (Reference) + + +
+
RTL gradient and text
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl.html new file mode 100644 index 000000000000..46786aa24690 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-clip-rtl.html @@ -0,0 +1,25 @@ + + +Single-axis scroll containers: Clipped X Rendering RTL + + + +
+
RTL gradient and text
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-expected.html new file mode 100644 index 000000000000..a87f5c67fac6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-expected.html @@ -0,0 +1,43 @@ + + + + + Visual clamp single-axis overflow: scroll to clip on dynamic style changes (Reference) + + + +
+
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-ref.html new file mode 100644 index 000000000000..a87f5c67fac6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip-ref.html @@ -0,0 +1,43 @@ + + + + + Visual clamp single-axis overflow: scroll to clip on dynamic style changes (Reference) + + + +
+
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip.html new file mode 100644 index 000000000000..8d7e84aeba0e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-overflow-scroll-to-clip.html @@ -0,0 +1,91 @@ + + + + + Single-axis scroll containers visually clamp disabled axes on dynamic style changes + + + + + + + +
+
+
+
+
+
+
+
+ + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic-expected.txt new file mode 100644 index 000000000000..e96c7307d018 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic-expected.txt @@ -0,0 +1,3 @@ + +FAIL changing from overflow: hidden to a single-axis scroller clamps the disabled axis and its scroll dimensions assert_equals: expected 0 but got 50 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic.html new file mode 100644 index 000000000000..92b890a48951 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-dynamic.html @@ -0,0 +1,51 @@ + + +Single-axis scroll containers clamp disabled axes on dynamic style changes + + + + + + + +
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic-expected.txt new file mode 100644 index 000000000000..fa562ff7d0b3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic-expected.txt @@ -0,0 +1,6 @@ + +FAIL LTR clipped X assert_equals: LTR clipped X: scrollTo ignores the clipped X-axis expected 0 but got 40 +FAIL RTL clipped X assert_equals: RTL clipped X: scrollTo ignores the clipped X-axis (negative in RTL) expected 0 but got -40 +FAIL LTR clipped Y assert_equals: LTR clipped Y: scrollTo ignores the clipped Y-axis expected 0 but got 50 +FAIL RTL clipped Y assert_equals: RTL clipped Y: scrollTo ignores the clipped Y-axis expected 0 but got 50 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic.html new file mode 100644 index 000000000000..1f8d95b4fa49 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-apis-programmatic.html @@ -0,0 +1,161 @@ + + +Single-axis scroll containers with programmatic scroll APIs + + + + + + + + + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-expected.txt new file mode 100644 index 000000000000..e9a21f7ff911 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-expected.txt @@ -0,0 +1,3 @@ + +FAIL scrollIntoView() respects single-axis limits independently on each ancestor assert_equals: inner ignores the clipped vertical axis expected 0 but got 20 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl-expected.txt new file mode 100644 index 000000000000..48d36a180a1c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl-expected.txt @@ -0,0 +1,3 @@ + +FAIL scrollIntoView() respects single-axis limits independently on each ancestor in RTL assert_equals: inner ignores the clipped vertical axis expected 0 but got 20 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl.html new file mode 100644 index 000000000000..56cfe21c3350 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view-rtl.html @@ -0,0 +1,77 @@ + + +Single-axis scroll containers with scrollIntoView (RTL) + + + + + + + +
+ +
+
+
+
+
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view.html new file mode 100644 index 000000000000..aba04f8fe9b2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/single-axis-scroll-into-view.html @@ -0,0 +1,76 @@ + + +Single-axis scroll containers with scrollIntoView + + + + + + + +
+ +
+
+
+
+
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-001-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-001-expected.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-001-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-001-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-001.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-001.html similarity index 88% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-001.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-001.html index ae9029933a6a..2fdcf8233e8a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-001.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-001.html @@ -3,7 +3,7 @@ CSS Basic User Interface Test: text-overflow - clip - the text inline content overflows will be broken - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-002-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-002-expected.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-002-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-002-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-002.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-002.html similarity index 88% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-002.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-002.html index d8a95299d29d..717c508eb019 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-002.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-002.html @@ -3,7 +3,7 @@ CSS Basic User Interface Test: text-overflow - ellipsis - the broken textual content instead of ellipsis - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-003-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-003-expected.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-003-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-003-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-003.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-003.html similarity index 88% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-003.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-003.html index ea958ef35b15..8c7ea45fad13 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-003.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-003.html @@ -3,7 +3,7 @@ CSS Basic User Interface Test: text-overflow - inherit - inherit clip value of parent's text-overflow property - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-004-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-004-expected.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-004-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-004-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-004.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-004.html similarity index 89% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-004.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-004.html index 1cc11e82573a..760fd685c20b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-004.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-004.html @@ -3,7 +3,7 @@ CSS Basic User Interface Test: text-overflow - inherit - inherit ellipsis value of parent's text-overflow property - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-005-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-005-expected.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-005-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-005-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-005.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-005.html similarity index 86% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-005.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-005.html index b7ced3673dce..72d29577e1da 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-005.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-005.html @@ -4,7 +4,7 @@ CSS-UI test: text-overflow reflow - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-006-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-006-expected.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-006-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-006-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-006.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-006.html similarity index 93% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-006.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-006.html index 8fd149a98db0..238a17be2ae1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-006.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-006.html @@ -2,7 +2,7 @@ CSS Basic User Interface Test: text-overflow applied at edge of block container - + + + +

Test passes if there is a filled green square and no red.

+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-007.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-007.html similarity index 95% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-007.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-007.html index 795dae40fa4a..5b14dd334b99 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-007.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-007.html @@ -2,7 +2,7 @@ CSS Basic User Interface Test: ellipsis adjacent to remaining content - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-008-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-008-expected.html similarity index 100% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-008-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-008-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-008.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-008.html similarity index 71% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-008.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-008.html index e4dbd761a05d..2128179174f2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-008.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-008.html @@ -2,13 +2,13 @@ CSS Basic User Interface Test: ellipsis and first character - + + + +

Test passes if there is a filled green square and no red.

+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-009.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-009.html similarity index 90% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-009.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-009.html index 384f82051199..d8ea68f38368 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-009.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-009.html @@ -2,7 +2,7 @@ CSS Basic User Interface Test: ellipsis and first atomic inline - + + + +

Test passes if there is a filled green square and no red.

+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-010.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-010.html similarity index 94% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-010.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-010.html index aec768cb6979..687d20cef9fc 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-010.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-010.html @@ -2,7 +2,7 @@ CSS Basic User Interface Test: ellipsis hides atomic inlines and chars at end of line - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-011-expected.xht b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-011-expected.xht new file mode 100644 index 000000000000..05a13794482a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-011-expected.xht @@ -0,0 +1,19 @@ + + + + CSS Reftest Reference + + + + +

Test passes if there is a filled green square and no red.

+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-011.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-011.html similarity index 93% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-011.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-011.html index 0fe96e65193b..6392a51a412d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-011.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-011.html @@ -2,7 +2,7 @@ CSS Basic User Interface Test: ellipsis hides end of line - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-012-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-012-expected.html similarity index 95% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-012-expected.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-012-expected.html index 700f9c896fe9..707f93d79c49 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-012-expected.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-012-expected.html @@ -9,7 +9,7 @@ color: green; } span { - color: white; + color: white; } diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-012.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-012.html similarity index 95% rename from LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-012.html rename to LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-012.html index a75908026aa3..15e31deda7d6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/text-overflow-012.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/text-overflow-012.html @@ -3,7 +3,7 @@ CSS Basic User Interface Test: ellipsis and extended grapheme cluster - +