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

View file

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

View file

@ -12,6 +12,8 @@
#include <LibWeb/DOM/Event.h> #include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/Node.h> #include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/ShadowRoot.h> #include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h> #include <LibWeb/HighResolutionTime/TimeOrigin.h>
namespace Web::DOM { namespace Web::DOM {
@ -263,4 +265,13 @@ Vector<GC::Root<EventTarget>> Event::composed_path() const
return composed_path; 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; } GC::Ptr<EventTarget> current_target() const { return m_current_target; }
void set_current_target(EventTarget* current_target) { m_current_target = 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; } bool return_value() const { return !m_cancelled; }
void set_return_value(bool return_value) void set_return_value(bool return_value)
{ {

View file

@ -9,7 +9,7 @@ interface Event {
readonly attribute DOMString type; readonly attribute DOMString type;
readonly attribute EventTarget? target; readonly attribute EventTarget? target;
readonly attribute EventTarget? srcElement; // legacy readonly attribute EventTarget? srcElement; // legacy
readonly attribute EventTarget? currentTarget; [ImplementedAs=current_target_for_bindings] readonly attribute EventTarget? currentTarget;
sequence<EventTarget> composedPath(); sequence<EventTarget> composedPath();
const unsigned short NONE = 0; 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. // 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. // 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* wrapped_event = &event;
auto result = WebIDL::call_user_object_operation(callback, "handleEvent"_utf16_fly_string, this_value, { { wrapped_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/PropertyDescriptor.h>
#include <LibJS/Runtime/PropertyKey.h> #include <LibJS/Runtime/PropertyKey.h>
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/HTML/CrossOrigin/AbstractOperations.h> #include <LibWeb/HTML/CrossOrigin/AbstractOperations.h>
#include <LibWeb/HTML/CrossOrigin/Reporting.h> #include <LibWeb/HTML/CrossOrigin/Reporting.h>
#include <LibWeb/HTML/Scripting/Environments.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 // 7.4 The WindowProxy exotic object, https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object
WindowProxy::WindowProxy(JS::Realm& realm) 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 <LibGC/Ptr.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/Export.h> #include <LibWeb/Export.h>
#include <LibWeb/Forward.h> #include <LibWeb/Forward.h>
namespace Web::HTML { namespace Web::HTML {
class WEB_API WindowProxy final : public JS::Object { class WEB_API WindowProxy final : public DOM::EventTarget {
JS_OBJECT(WindowProxy, JS::Object); WEB_PLATFORM_OBJECT(WindowProxy, DOM::EventTarget)
GC_DECLARE_ALLOCATOR(WindowProxy); GC_DECLARE_ALLOCATOR(WindowProxy);
public: public:
@ -33,8 +34,6 @@ public:
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override; virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override;
virtual JS::ThrowCompletionOr<GC::RootVector<JS::Value>> internal_own_property_keys() 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; } GC::Ptr<Window> window() const { return m_window; }
void set_window(GC::Ref<Window>); void set_window(GC::Ref<Window>);
@ -43,6 +42,8 @@ public:
private: private:
explicit WindowProxy(JS::Realm&); 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 bool is_html_window_proxy() const override { return true; }
virtual void visit_edges(JS::Cell::Visitor&) override; virtual void visit_edges(JS::Cell::Visitor&) override;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests Found 1 tests
1 Fail 1 Pass
Fail Event propagation path when an element in it is moved within the DOM 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 Found 1 tests
1 Fail 1 Pass
Fail Event propagation path when an element in it is removed from the DOM 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>