Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/WebKit/WebKit
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Jan 27, 2025
2 parents 42ba726 + 4ceef70 commit 9af9ea1
Show file tree
Hide file tree
Showing 53 changed files with 463 additions and 269 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This test passes ensures we don't crash after performing accessibility tree updates with dirty style and object relations.

PASS: No crash.
PASS successfullyParsed is true

TEST COMPLETE

69 changes: 69 additions & 0 deletions LayoutTests/accessibility/dirty-style-and-relations-crash.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/accessibility-helper.js"></script>
<script src="../resources/js-test.js"></script>
</head>
<body>

<main aria-label="content here">
<div>
<div id="container" role="group" aria-label="container">
<button id="button">Foo</button>
</div>

<div id="container-2" role="group" aria-label="container2" aria-owns="button">
</div>

<div id="container-3" role="group" aria-labelledby="text" aria-owns="abc">
</div>
<div>
</main>

<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="d"></div>
<div id="e"></div>

<div role="group" id="text">hello world</div>

<script>
var output = "This test passes ensures we don't crash after performing accessibility tree updates with dirty style and object relations.\n\n";

if (window.accessibilityController) {
window.jsTestIsAsync = true;

touchAccessibilityTree(accessibilityController.rootElement);
setTimeout(async function() {
// Wait out any tree updates that result from the initial page load that didn't already happen during the
// touchAccessibilityTree call above.
await sleep(100);

// Changing the id attribute unconditionally dirties relations, so do that to setup for the crash.
document.getElementById("button").setAttribute("id", "abc");
// Dirty the style of #text and #abc, which are involved in relations — this is necesssary to trigger the crash.
// The goal is to get `Element::computedStyle()` to call `resolveComputedStyle()` at an invalid time.
document.getElementById("text").style.fontSize = "16px";
document.getElementById("c").setAttribute("id", "c1");
document.getElementById("text").style.display = "none"
document.getElementById("abc").style.fontSize = "16px";
document.getElementById("abc").style.display = "block";
// Now that style and relations are dirty, perform a DOM mutation, which should cause AXIsolatedTree::removeNode
// to run and try to un-dirty relations as a result of a parentObject() call. This will then cause isIgnored()
// to run, which depends on style. If we resolve style eagerly at this point, we will RELEASE_ASSERT in RenderTreeBuilder
// because a render tree update is already happening higher up in the stack.
document.getElementById("container").removeChild(document.getElementById("abc"));
document.getElementById("d").setAttribute("id", "d1");
document.getElementById("e").setAttribute("id", "e1");

touchAccessibilityTree(accessibilityController.rootElement);

output += "PASS: No crash."
debug(output);
finishJSTest();
}, 0);
}
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
if (context) {
context.fillStyle = "#ff0000";
context.fillRect(0, 0, 200, 200);
if (Date.now() - drawStartTime < 500) {
if (Date.now() - drawStartTime < 1000) {
window.requestAnimationFrame(doRedImageDraw);
} else {
drawStartTime = Date.now();
Expand All @@ -51,7 +51,15 @@
}
}

async_test(t => {
function waitForEvent(object, eventName, testName)
{
return new Promise((resolve, reject) => {
object.addEventListener(eventName, (e) => resolve(e), { once: true });
setTimeout(() => reject("waitForEvent " + (testName ? (testName + " ") : "") + "timed out for " + eventName), 2500);
});
}

promise_test(async (test) => {
const ac = new AudioContext();
const osc = ac.createOscillator();
const dest = ac.createMediaStreamDestination();
Expand All @@ -64,48 +72,47 @@
video.addTrack(audio.getAudioTracks()[0]);
assert_equals(video.getAudioTracks().length, 1, "video mediastream starts with one audio track");
const recorder = new MediaRecorder(video);
let mode = 0;

recorder.ondataavailable = t.step_func(blobEvent => {
assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent');
assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable');
assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob');
assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers');
player.src = window.URL.createObjectURL(blobEvent.data);
const resFrame = document.getElementById("frame");
const resContext = resFrame.getContext('2d');

player.oncanplay = t.step_func(() => {
assert_greater_than(player.duration, 0.1, 'the duration should be greater than 100ms');
player.play();
});
player.onplay = () => {
player.pause();
player.currentTime = 0.05;
};
player.onseeked = t.step_func(() => {
resContext.drawImage(player, 0, 0);
if (!mode) {
_assertPixelApprox(resFrame, 25, 25, 255, 0, 0, 255, "25, 25", "255, 0, 0, 255", 50);
_assertPixelApprox(resFrame, 50, 50, 255, 0, 0, 255, "50, 50", "255, 0, 0, 255", 50);
mode = 1;
player.currentTime = Math.min(1.5, player.duration - 0.05);
} else {
_assertPixelApprox(resFrame, 20, 20, 0, 255, 0, 255, "20, 20", "0, 255, 0, 255", 50);
_assertPixelApprox(resFrame, 199, 199, 0, 255, 0, 255, "199, 199", "0, 255, 0, 255", 50);
t.done();
}
});
player.load();
});
drawStartTime = Date.now();
doRedImageDraw();
recorder.start();
assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully');
setTimeout(() => {
recorder.stop();
}, 2000);
setTimeout(() => { recorder.stop(); }, 2000);

const blobEvent = await waitForEvent(recorder, 'dataavailable');
assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent');
assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable');
assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob');
assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers');

const MediaSource = self.ManagedMediaSource || self.MediaSource;
player.disableRemotePlayback = true;
const source = new MediaSource();
player.src = URL.createObjectURL(source);
await waitForEvent(source, 'sourceopen');
const sourceBuffer = source.addSourceBuffer("video/mp4");
sourceBuffer.appendBuffer(await blobEvent.data.arrayBuffer());
await waitForEvent(sourceBuffer, 'updateend');
source.endOfStream();

const resFrame = document.getElementById("frame");
const resContext = resFrame.getContext('2d');

assert_greater_than(player.duration, 1, 'the duration should be greater than 1s');
await player.play();

player.pause();
player.currentTime = Math.min(1.5, player.duration - 0.05);
await waitForEvent(player, 'seeked');
resContext.drawImage(player, 0, 0);
_assertPixelApprox(resFrame, 20, 20, 0, 255, 0, 255, "20, 20", "0, 255, 0, 255", 50);
_assertPixelApprox(resFrame, 199, 199, 0, 255, 0, 255, "199, 199", "0, 255, 0, 255", 50);

player.currentTime = 0;
await waitForEvent(player, 'seeked');
resContext.drawImage(player, 0, 0);
_assertPixelApprox(resFrame, 25, 25, 255, 0, 0, 255, "25, 25", "255, 0, 0, 255", 50);
_assertPixelApprox(resFrame, 50, 50, 255, 0, 0, 255, "50, 50", "255, 0, 0, 255", 50);
}, 'MediaRecorder can successfully record the video for a audio-video stream');

</script>
Expand Down
20 changes: 20 additions & 0 deletions LayoutTests/media/media-source/media-source-reopen-expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

RUN(video.src = URL.createObjectURL(source))
EVENT(sourceopen)
RUN(audiosb = source.addSourceBuffer("audio/mp4; codecs=mp4a.40.2"))
RUN(videosb = source.addSourceBuffer("video/mp4; codecs=avc1.4d401e"))
RUN(audiosb.appendWindowEnd = 0.4)
RUN(videosb.appendWindowEnd = 0.5)
EXPECTED (video.buffered.end(video.buffered.length-1) == Math.min(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1)) == 'true') OK
RUN(source.endOfStream())
EXPECTED (source.readyState == 'ended') OK
EXPECTED (video.buffered.end(video.buffered.length-1) == Math.max(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1)) == 'true') OK
EXPECTED (video.duration == Math.max(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1)) == 'true') OK
EVENT(sourceended)
EXPECTED (source.readyState == 'open') OK
EXPECTED (video.buffered.end(video.buffered.length-1) == Math.min(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1)) == 'true') OK
EVENT(update)
RUN(source.endOfStream())
EVENT(ended)
END OF TEST

63 changes: 63 additions & 0 deletions LayoutTests/media/media-source/media-source-reopen.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>media-source-reopen</title>
<script src="../video-test.js"></script>
<script>
var loader;
var source;
var audiosb;
var videosb;
var audiodata;
var videodata;

async function startTest()
{
findMediaElement();
video.disableRemotePlayback = true;
video.muted = true;

const MediaSource = self.ManagedMediaSource || self.MediaSource;

source = new MediaSource();
run('video.src = URL.createObjectURL(source)');
await waitFor(source, 'sourceopen');
waitFor(video, 'error').then(failTest);

run('audiosb = source.addSourceBuffer("audio/mp4; codecs=mp4a.40.2")');
run('videosb = source.addSourceBuffer("video/mp4; codecs=avc1.4d401e")');

let audioResponse = await fetch('content/test-48kHz.m4a');
let audiodata = await audioResponse.arrayBuffer();
let videoResponse = await fetch('content/test-fragmented-video.mp4');
let videodata = await videoResponse.arrayBuffer();

run('audiosb.appendWindowEnd = 0.4');
run('videosb.appendWindowEnd = 0.5');

audiosb.appendBuffer(audiodata);
videosb.appendBuffer(videodata);
await Promise.all([waitFor(audiosb, 'update', true), waitFor(videosb, 'update', true)]);

testExpected('video.buffered.end(video.buffered.length-1) == Math.min(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1))', true);
run('source.endOfStream()');
testExpected('source.readyState', "ended");
testExpected('video.buffered.end(video.buffered.length-1) == Math.max(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1))', true);
testExpected('video.duration == Math.max(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1))', true);
await waitFor(source, 'sourceended');

audiosb.appendBuffer(audiodata);
testExpected('source.readyState', "open");
testExpected('video.buffered.end(video.buffered.length-1) == Math.min(audiosb.buffered.end(audiosb.buffered.length-1), videosb.buffered.end(videosb.buffered.length-1))', true);
await waitFor(audiosb, 'update');
run('source.endOfStream()');

await video.play();
waitForEventAndEnd('ended');
}
</script>
</head>
<body onload="startTest()">
<video controls></video>
</body>
</html>
1 change: 1 addition & 0 deletions LayoutTests/platform/ios/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -2300,6 +2300,7 @@ accessibility/button-inside-label-ax-text.html [ Pass ]
accessibility/changing-aria-hidden-with-display-none-parent.html [ Pass ]
accessibility/checkbox-mixed-value.html [ Pass ]
accessibility/dialog-slotted-content.html [ Pass ]
accessibility/dirty-style-and-relations-crash.html [ Pass ]
accessibility/display-contents/dynamically-added-children.html [ Pass ]
accessibility/display-contents/element-roles.html [ Pass ]
accessibility/display-contents/listbox-item.html [ Pass ]
Expand Down
3 changes: 2 additions & 1 deletion LayoutTests/platform/mac-wk2/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -1915,7 +1915,8 @@ webkit.org/b/283144 [ Sequoia ] http/tests/storageAccess/grant-storage-access-un
webkit.org/b/283144 [ Sequoia ] http/tests/storageAccess/request-and-grant-access-then-navigate-cross-site-should-not-have-access.https.html [ Failure ]
webkit.org/b/283144 [ Sequoia ] http/tests/storageAccess/request-and-grant-access-then-navigate-same-site-should-have-access.https.html [ Failure ]

webkit.org/b/283210 http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html [ Pass Failure ]
# On x86_64 Ventura bots, stream recording doesnt always complete within the 2s.
[ Ventura x86_64 ] http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html [ Pass Failure ]

webkit.org/b/283596 [ Sequoia+ Debug ] ipc/cfnetwork-crashes-with-string-to-string-http-headers.html [ Skip ]

Expand Down
17 changes: 15 additions & 2 deletions Source/WTF/wtf/spi/darwin/XPCSPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include <os/object.h>
#include <span>
#include <wtf/StdLibExtras.h>
#include <wtf/text/ASCIILiteral.h>
#include <wtf/text/WTFString.h>

#if HAVE(XPC_API) || USE(APPLE_INTERNAL_SDK)
#include <xpc/xpc.h>
Expand Down Expand Up @@ -256,9 +258,20 @@ void xpc_release(xpc_object_t);

WTF_EXTERN_C_END

inline std::span<const uint8_t> xpc_dictionary_get_data_span(xpc_object_t xdict, const char* key)
inline std::span<const uint8_t> xpc_dictionary_get_data_span(xpc_object_t xdict, ASCIILiteral key)
{
size_t dataSize { 0 };
auto* data = static_cast<const uint8_t*>(xpc_dictionary_get_data(xdict, key, &dataSize));
auto* data = static_cast<const uint8_t*>(xpc_dictionary_get_data(xdict, key.characters(), &dataSize)); // NOLINT
return unsafeMakeSpan(data, dataSize);
}

// ASCIILiteral version of XPC_ERROR_KEY_DESCRIPTION.
static constexpr auto xpcErrorDescriptionKey = "XPCErrorDescription"_s;

inline String xpc_dictionary_get_wtfstring(xpc_object_t xdict, ASCIILiteral key)
{
auto* cstring = xpc_dictionary_get_string(xdict, key.characters()); // NOLINT
if (!cstring)
return { };
return String::fromUTF8(cstring);
}
3 changes: 2 additions & 1 deletion Source/WebCore/Modules/mediasource/MediaSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1419,8 +1419,9 @@ void MediaSource::onReadyStateChange(ReadyState oldState, ReadyState newState)
// https://w3c.github.io/media-source/#htmlmediaelement-extensions-buffered
for (auto& sourceBuffer : m_sourceBuffers.get())
sourceBuffer->setMediaSourceEnded(true);
updateBufferedIfNeeded(true /* force */);
}
if (newState == ReadyState::Ended || (newState == ReadyState::Open && oldState == ReadyState::Ended))
updateBufferedIfNeeded(true /* force */);

// MediaSource's readyState transitions from "open" to "closed" or "ended" to "closed".
if (oldState > ReadyState::Closed && newState == ReadyState::Closed) {
Expand Down
8 changes: 8 additions & 0 deletions Source/WebCore/PAL/pal/spi/cg/CoreGraphicsSPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>
#include <wtf/text/WTFString.h>

#if HAVE(IOSURFACE)
#include <wtf/spi/cocoa/IOSurfaceSPI.h>
Expand Down Expand Up @@ -449,3 +450,10 @@ CG_EXTERN void CGEnterLockdownModeForFonts();
#endif

WTF_EXTERN_C_END

inline String CGPDFDictionaryGetNameString(CGPDFDictionaryRef dictionary, ASCIILiteral key)
{
const char* value = nullptr;
CGPDFDictionaryGetName(dictionary, key.characters(), &value);
return value ? String::fromUTF8(value) : String();
}
8 changes: 4 additions & 4 deletions Source/WebCore/PAL/pal/text/EncodingTables.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ template<typename CollectionType> void stableSortByFirst(CollectionType&);
template<typename CollectionType> bool isSortedByFirst(const CollectionType&);
template<typename CollectionType> bool sortedFirstsAreUnique(const CollectionType&);
template<typename CollectionType, typename KeyType> static auto findFirstInSortedPairs(const CollectionType& sortedPairsCollection, const KeyType&) -> std::optional<decltype(std::begin(sortedPairsCollection)->second)>;
template<typename CollectionType, typename KeyType> static auto findInSortedPairs(const CollectionType& sortedPairsCollection, const KeyType&) -> std::pair<decltype(std::begin(sortedPairsCollection)), decltype(std::begin(sortedPairsCollection))>;
template<typename CollectionType, typename KeyType> static auto findInSortedPairs(const CollectionType& sortedPairsCollection, const KeyType&) -> std::span<std::remove_reference_t<decltype(*std::begin(sortedPairsCollection))>>;

#if !ASSERT_ENABLED
inline void checkEncodingTableInvariants() { }
Expand Down Expand Up @@ -124,12 +124,12 @@ template<typename CollectionType, typename KeyType> static auto findFirstInSorte
return iterator->second;
}

template<typename CollectionType, typename KeyType> static auto findInSortedPairs(const CollectionType& collection, const KeyType& key) -> std::pair<decltype(std::begin(collection)), decltype(std::begin(collection))> {
template<typename CollectionType, typename KeyType> static auto findInSortedPairs(const CollectionType& collection, const KeyType& key) -> std::span<std::remove_reference_t<decltype(*std::begin(collection))>> {
if constexpr (std::is_integral_v<KeyType>) {
if (key != decltype(std::begin(collection)->first)(key))
return { std::end(collection), std::end(collection) };
return { };
}
return std::equal_range(std::begin(collection), std::end(collection), makeFirstAdapter(key), CompareFirst { });
return std::ranges::equal_range(collection, makeFirstAdapter(key), CompareFirst { });
}

}
Loading

0 comments on commit 9af9ea1

Please sign in to comment.