Compare commits

...

3 commits

Author SHA1 Message Date
mikiubo
d4df0e1db9 LibWeb: Make Event.currentTarget return WindowProxy instead of Window
Make WindowProxy implement the EventTarget interface. Add a new method
current_target_for_bindings() that returns a WindowProxy object instead
of directly exposing the Window object.

These changes fixes several WPT tests that contain `window ===
currentTarget`.
2025-10-15 15:36:34 +02:00
Jelle Raaijmakers
373b2838db CI: Don't run merge conflict labeler on forks 2025-10-15 10:36:07 +02:00
Jelle Raaijmakers
980e715668 CI: Run js-benchmarks with optional wasm binary
If we run js-benchmarks against older builds that did not yet include
the wasm repl, make sure we skip extracting the file and passing the
`--wasm-executable` argument.
2025-10-15 10:36:07 +02:00
19 changed files with 122 additions and 36 deletions

View file

@ -53,7 +53,6 @@ jobs:
sudo apt-get install -y python3-venv
- name: 'Download JS repl artifact'
id: download-js-artifact
uses: dawidd6/action-download-artifact@v11
with:
run_id: ${{ github.event.workflow_run.id }}
@ -73,15 +72,16 @@ jobs:
commit_hash=$(cat js-repl/COMMIT)
echo "sha=${commit_hash}" >> "${GITHUB_OUTPUT}"
- name: 'Download Wasm repl artifact'
id: download-wasm-artifact
- name: 'Download wasm repl artifact'
uses: dawidd6/action-download-artifact@v11
with:
run_id: ${{ github.event.workflow_run.id }}
name: ladybird-wasm-${{ matrix.package_type }}
path: wasm-repl
if_no_artifact_found: warn
- name: 'Extract Wasm repl'
- name: 'Extract wasm repl'
if: ${{ hashFiles('wasm-repl/*.tar.gz') != '' }}
shell: bash
run: |
cd wasm-repl
@ -95,17 +95,24 @@ jobs:
source .venv/bin/activate
python3 -m pip install -r requirements.txt
run_options="--iterations=5"
run_options='--iterations=5'
js_path="${{ github.workspace }}/js-repl/bin/js"
run_options="${run_options} --executable=${js_path}"
wasm_path="${{ github.workspace }}/wasm-repl/bin/wasm"
if [ -x "${wasm_path}" ]; then
run_options="${run_options} --wasm-executable=${wasm_path}"
else
echo "Wasm repl not found; skipping wasm benchmarks."
fi
if [ "${{ github.event.workflow_run.event }}" = "workflow_dispatch" ]; then
# Upstream was run manually; we might run into failing benchmarks as a result of older builds.
run_options="${run_options} --continue-on-failure"
fi
./run.py \
--executable=${{ github.workspace }}/js-repl/bin/js \
--wasm-executable=${{ github.workspace }}/wasm-repl/bin/wasm \
${run_options}
./run.py ${run_options}
- name: 'Save results as an artifact'
uses: actions/upload-artifact@v4

View file

@ -13,9 +13,12 @@ on:
jobs:
auto-labeler:
runs-on: blacksmith-2vcpu-ubuntu-2404
if: github.repository == 'LadybirdBrowser/ladybird'
permissions:
contents: read
pull-requests: write
steps:
- uses: eps1lon/actions-label-merge-conflict@v3
with:

View file

@ -12,6 +12,8 @@
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
namespace Web::DOM {
@ -263,4 +265,13 @@ Vector<GC::Root<EventTarget>> Event::composed_path() const
return composed_path;
}
// AD-HOC: Needed to return a WindowProxy when the current target is a Window,
// ensuring that JavaScript sees the correct global object instead of the internal Window.
GC::Ptr<EventTarget> Event::current_target_for_bindings() const
{
if (auto* window = as_if<HTML::Window>(m_current_target.ptr()))
return window->window();
return m_current_target;
}
}

View file

@ -101,6 +101,8 @@ public:
GC::Ptr<EventTarget> current_target() const { return m_current_target; }
void set_current_target(EventTarget* current_target) { m_current_target = current_target; }
GC::Ptr<EventTarget> current_target_for_bindings() const;
bool return_value() const { return !m_cancelled; }
void set_return_value(bool return_value)
{

View file

@ -9,7 +9,7 @@ interface Event {
readonly attribute DOMString type;
readonly attribute EventTarget? target;
readonly attribute EventTarget? srcElement; // legacy
readonly attribute EventTarget? currentTarget;
[ImplementedAs=current_target_for_bindings] readonly attribute EventTarget? currentTarget;
sequence<EventTarget> composedPath();
const unsigned short NONE = 0;

View file

@ -89,7 +89,7 @@ bool EventDispatcher::inner_invoke(Event& event, Vector<GC::Root<DOM::DOMEventLi
// 11. Call a user object’s operation with listener’s callback, "handleEvent", « event », and event’s currentTarget attribute value.
// FIXME: These should be wrapped for us in call_user_object_operation, but it currently doesn't do that.
auto* this_value = event.current_target().ptr();
auto* this_value = event.current_target_for_bindings().ptr();
auto* wrapped_event = &event;
auto result = WebIDL::call_user_object_operation(callback, "handleEvent"_utf16_fly_string, this_value, { { wrapped_event } });

View file

@ -11,6 +11,7 @@
#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/PropertyKey.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/HTML/CrossOrigin/AbstractOperations.h>
#include <LibWeb/HTML/CrossOrigin/Reporting.h>
#include <LibWeb/HTML/Scripting/Environments.h>
@ -24,7 +25,7 @@ GC_DEFINE_ALLOCATOR(WindowProxy);
// 7.4 The WindowProxy exotic object, https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object
WindowProxy::WindowProxy(JS::Realm& realm)
: JS::Object(realm, nullptr, MayInterfereWithIndexedPropertyAccess::Yes)
: DOM::EventTarget(realm, MayInterfereWithIndexedPropertyAccess::Yes)
{
}

View file

@ -10,13 +10,14 @@
#include <LibGC/Ptr.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/Object.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
namespace Web::HTML {
class WEB_API WindowProxy final : public JS::Object {
JS_OBJECT(WindowProxy, JS::Object);
class WEB_API WindowProxy final : public DOM::EventTarget {
WEB_PLATFORM_OBJECT(WindowProxy, DOM::EventTarget)
GC_DECLARE_ALLOCATOR(WindowProxy);
public:
@ -33,8 +34,6 @@ public:
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override;
virtual JS::ThrowCompletionOr<GC::RootVector<JS::Value>> internal_own_property_keys() const override;
virtual bool eligible_for_own_property_enumeration_fast_path() const override final { return false; }
GC::Ptr<Window> window() const { return m_window; }
void set_window(GC::Ref<Window>);
@ -43,6 +42,8 @@ public:
private:
explicit WindowProxy(JS::Realm&);
virtual bool is_window_or_worker_global_scope_mixin() const final { return true; }
virtual bool is_html_window_proxy() const override { return true; }
virtual void visit_edges(JS::Cell::Visitor&) override;

View file

@ -2,9 +2,8 @@ Harness status: OK
Found 5 tests
4 Pass
1 Fail
Fail In window.document with click event
5 Pass
Pass In window.document with click event
Pass In window.document with load event
Pass In window.document.cloneNode(true)
Pass In new Document()

View file

@ -2,9 +2,8 @@ Harness status: OK
Found 5 tests
4 Pass
1 Fail
Fail In window.document with click event
5 Pass
Pass In window.document with click event
Pass In window.document with load event
Pass In window.document.cloneNode(true)
Pass In new Document()

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Dispatch additional events inside an event listener
1 Pass
Pass Dispatch additional events inside an event listener

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Multiple dispatchEvent() and cancelBubble
1 Pass
Pass Multiple dispatchEvent() and cancelBubble

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Multiple dispatchEvent() and stopPropagation()
1 Pass
Pass Multiple dispatchEvent() and stopPropagation()

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail EventTarget.addEventListener with the capture argument omitted
1 Pass
Pass EventTarget.addEventListener with the capture argument omitted

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Dispatch additional events inside an event listener
1 Pass
Pass Dispatch additional events inside an event listener

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Event propagation path when an element in it is moved within the DOM
1 Pass
Pass Event propagation path when an element in it is moved within the DOM

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Event propagation path when an element in it is removed from the DOM
1 Pass
Pass Event propagation path when an element in it is removed from the DOM

View file

@ -0,0 +1,8 @@
Harness status: OK
Found 3 tests
3 Pass
Pass EventTarget methods on Window instances are inherited from the EventTarget prototype
Pass window.addEventListener respects custom `this`
Pass window.addEventListener treats nullish `this` as `window`

View file

@ -0,0 +1,55 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Window extends EventTarget</title>
<link rel="help" href="https://github.com/jsdom/jsdom/issues/2830">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
"use strict";
test(() => {
assert_equals(window.addEventListener, EventTarget.prototype.addEventListener);
assert_equals(window.removeEventListener, EventTarget.prototype.removeEventListener);
assert_equals(window.dispatchEvent, EventTarget.prototype.dispatchEvent);
}, "EventTarget methods on Window instances are inherited from the EventTarget prototype");
test(() => {
const kCustom = "custom-event";
const customEvent = new CustomEvent(kCustom, {
bubbles: true
});
let target;
window.addEventListener.call(document.body, kCustom, function () {
target = this;
});
document.body.dispatchEvent(customEvent);
assert_equals(target, document.body);
}, "window.addEventListener respects custom `this`");
test(() => {
const kCustom = "custom-event";
const customEvent = new CustomEvent(kCustom, {
bubbles: true
});
let target;
window.addEventListener.call(null, kCustom, function () {
target = this;
});
document.body.dispatchEvent(customEvent);
assert_equals(target, window);
}, "window.addEventListener treats nullish `this` as `window`");
</script>