From 98c00f4b45dfe52737aa194b4b4a7542e26d6d15 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Mon, 8 Sep 2025 15:51:56 +0200 Subject: [PATCH] Tests: Import `resize-observer/observe.html` WPT test --- .../wpt-import/resize-observer/observe.txt | 27 + .../wpt-import/resize-observer/observe.html | 1007 +++++++++++++++++ .../resize-observer/resources/iframe.html | 38 + .../resources/resizeTestHelper.js | 195 ++++ 4 files changed, 1267 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/resize-observer/observe.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/resize-observer/observe.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/iframe.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/resizeTestHelper.js diff --git a/Tests/LibWeb/Text/expected/wpt-import/resize-observer/observe.txt b/Tests/LibWeb/Text/expected/wpt-import/resize-observer/observe.txt new file mode 100644 index 00000000000..ea20142331f --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/resize-observer/observe.txt @@ -0,0 +1,27 @@ +Harness status: OK + +Found 21 tests + +10 Pass +11 Fail +Pass ResizeObserver implemented +Pass guard +Pass test0: simple observation +Pass test1: multiple observation on same element trigger only one +Pass test2: throw exception when observing non-element +Pass test3: disconnect stops all notifications +Pass test4: unobserve target stops notifications, unobserve non-observed does nothing +Fail test5: observe img +Pass test6: iframe notifications +Pass test7: callback.this +Fail test8: simple content-box observation +Fail test9: simple content-box observation but keep border-box size unchanged +Fail test10: simple border-box observation +Fail test11: simple observation with vertical writing mode +Fail test12: no observation is fired after the change of writing mode when box's specified size comes from logical size properties. +Fail test13: an observation is fired after the change of writing mode when box's specified size comes from physical size properties. +Fail test14: observe the same target but using a different box should override the previous one +Fail test15: an observation is fired with box dimensions 0 when element's display property is set to inline +Fail test16: observations fire once with 0x0 size for non-replaced inline elements +Fail test17: Box sizing snd Resize Observer notifications +Pass test18: an observation is fired when device-pixel-content-box is being observed \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/resize-observer/observe.html b/Tests/LibWeb/Text/input/wpt-import/resize-observer/observe.html new file mode 100644 index 00000000000..e554f48bee3 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/resize-observer/observe.html @@ -0,0 +1,1007 @@ + +ResizeObserver tests + + + + +
+ + diff --git a/Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/iframe.html b/Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/iframe.html new file mode 100644 index 00000000000..5c801e63682 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/iframe.html @@ -0,0 +1,38 @@ + + + + +

iframe test

+
t1
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/resizeTestHelper.js b/Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/resizeTestHelper.js new file mode 100644 index 00000000000..284a780c259 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/resize-observer/resources/resizeTestHelper.js @@ -0,0 +1,195 @@ +'use strict'; + +/** + ResizeTestHelper is a framework to test ResizeObserver + notifications. Use it to make assertions about ResizeObserverEntries. + This framework is needed because ResizeObservations are + delivered asynchronously inside the event loop. + + Features: + - can queue multiple notification steps in a test + - handles timeouts + - returns Promise that is fulfilled when test completes. + Use to chain tests (since parallel async ResizeObserver tests + would conflict if reusing same DOM elements). + + Usage: + + create ResizeTestHelper for every test. + Make assertions inside notify, timeout callbacks. + Start tests with helper.start() + Chain tests with Promises. + Counts animation frames, see startCountingRaf +*/ + +/* + @param name: test name + @param steps: + { + setup: function(ResizeObserver) { + // called at the beginning of the test step + // your observe/resize code goes here + }, + notify: function(entries, observer) { + // ResizeObserver callback. + // Make assertions here. + // Return true if next step should start on the next event loop. + }, + timeout: function() { + // Define this if your test expects to time out, and the expected timeout + // value will be 100ms. + // If undefined, timeout is assert_unreached after 1000ms. + } + } +*/ +function ResizeTestHelper(name, steps) +{ + this._name = name; + this._steps = steps || []; + this._stepIdx = -1; + this._harnessTest = null; + this._observer = new ResizeObserver(this._handleNotification.bind(this)); + this._timeoutBind = this._handleTimeout.bind(this); + this._nextStepBind = this._nextStep.bind(this); +} + +// The default timeout value in ms. +// We expect TIMEOUT to be longer than we would ever have to wait for notify() +// to be fired. This is used for tests which are not expected to time out, so +// it can be large without slowing down the test. +ResizeTestHelper.TIMEOUT = 1000; +// A reasonable short timeout value in ms. +// We expect SHORT_TIMEOUT to be long enough that notify() would usually get a +// chance to fire before SHORT_TIMEOUT expires. This is used for tests which +// *are* expected to time out (with no callbacks fired), so we'd like to keep +// it relatively short to avoid slowing down the test. +ResizeTestHelper.SHORT_TIMEOUT = 100; + +ResizeTestHelper.prototype = { + get _currentStep() { + return this._steps[this._stepIdx]; + }, + + _nextStep: function() { + if (++this._stepIdx == this._steps.length) + return this._done(); + // Use SHORT_TIMEOUT if this step expects timeout. + let timeoutValue = this._steps[this._stepIdx].timeout ? + ResizeTestHelper.SHORT_TIMEOUT : + ResizeTestHelper.TIMEOUT; + this._timeoutId = this._harnessTest.step_timeout( + this._timeoutBind, timeoutValue); + try { + this._steps[this._stepIdx].setup(this._observer); + } + catch(err) { + this._harnessTest.step(() => { + assert_unreached("Caught a throw, possible syntax error"); + }); + } + }, + + _handleNotification: function(entries) { + if (this._timeoutId) { + window.clearTimeout(this._timeoutId); + delete this._timeoutId; + } + this._harnessTest.step(() => { + try { + let rafDelay = this._currentStep.notify(entries, this._observer); + if (rafDelay) + window.requestAnimationFrame(this._nextStepBind); + else + this._nextStep(); + } + catch(err) { + this._harnessTest.step(() => { + throw err; + }); + // Force to _done() the current test. + this._done(); + } + }); + }, + + _handleTimeout: function() { + delete this._timeoutId; + this._harnessTest.step(() => { + if (this._currentStep.timeout) { + this._currentStep.timeout(); + } + else { + this._harnessTest.step(() => { + assert_unreached("Timed out waiting for notification. (" + ResizeTestHelper.TIMEOUT + "ms)"); + }); + } + this._nextStep(); + }); + }, + + _done: function() { + this._observer.disconnect(); + delete this._observer; + this._harnessTest.done(); + if (this._rafCountRequest) { + window.cancelAnimationFrame(this._rafCountRequest); + delete this._rafCountRequest; + } + window.requestAnimationFrame(() => { this._resolvePromise(); }); + }, + + start: function(cleanup) { + this._harnessTest = async_test(this._name); + + if (cleanup) { + this._harnessTest.add_cleanup(cleanup); + } + + this._harnessTest.step(() => { + assert_equals(this._stepIdx, -1, "start can only be called once"); + this._nextStep(); + }); + return new Promise( (resolve, reject) => { + this._resolvePromise = resolve; + this._rejectPromise = reject; + }); + }, + + get rafCount() { + if (!this._rafCountRequest) + throw "rAF count is not active"; + return this._rafCount; + }, + + get test() { + if (!this._harnessTest) { + throw "_harnessTest is not initialized"; + } + return this._harnessTest; + }, + + _incrementRaf: function() { + if (this._rafCountRequest) { + this._rafCount++; + this._rafCountRequest = window.requestAnimationFrame(this._incrementRafBind); + } + }, + + startCountingRaf: function() { + if (this._rafCountRequest) + window.cancelAnimationFrame(this._rafCountRequest); + if (!this._incrementRafBind) + this._incrementRafBind = this._incrementRaf.bind(this); + this._rafCount = 0; + this._rafCountRequest = window.requestAnimationFrame(this._incrementRafBind); + } +} + +function createAndAppendElement(tagName, parent) { + if (!parent) { + parent = document.body; + } + const element = document.createElement(tagName); + parent.appendChild(element); + return element; +}