5 Commits c5c3859205 ... 6bb0d585e3

Tác giả SHA1 Thông báo Ngày
  Andreas Kling 6bb0d585e3 LibJS: Elide function wrapper for class field literal initializers 1 tuần trước cách đây
  Aliaksandr Kalenik fd147e6be0 LibWeb: Protect SkiaBackendContext with a mutex 1 tuần trước cách đây
  Aliaksandr Kalenik 24527b6ae3 LibWeb: Pass PaintingSurface into DisplayListPlayer::execute() 1 tuần trước cách đây
  Timothy Flynn b23b21fa4a UI/AppKit: Restore custom cursor shown on link hover 1 tuần trước cách đây
  Timothy Flynn b64d450f32 CI: Set correct architecture for JS artifact builds 1 tuần trước cách đây

+ 8 - 5
.github/workflows/js-artifacts.yml

@@ -17,13 +17,16 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
+        os_name: ['Linux']
         os: [ubuntu-24.04]
+        arch: ['x86_64']
         package_type: [Linux-x86_64]
-        os_name: [Linux]
+
         include:
-          - os: macos-14
+          - os_name: 'macOS'
+            os: macos-15
+            arch: 'arm64'
             package_type: macOS-universal2
-            os_name: macOS
 
     concurrency:
       group: ${{ github.workflow }}-${{ matrix.os_name }}
@@ -36,14 +39,14 @@ jobs:
         uses: ./.github/actions/setup
         with:
           os: ${{ matrix.os_name }}
-          arch: 'Lagom'
+          arch: ${{ matrix.arch }}
 
       - name: Restore Caches
         uses: ./.github/actions/cache-restore
         id: 'cache-restore'
         with:
           os: ${{ matrix.os_name }}
-          arch: 'Lagom'
+          arch: ${{ matrix.arch }}
           cache_key_extra: 'LibJS Artifacts'
           ccache_path: ${{ env.CCACHE_DIR }}
           download_cache_path: ${{ github.workspace }}/Build/caches

+ 55 - 23
Libraries/LibGfx/PainterSkia.cpp

@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
- * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
+ * Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
  * Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -33,9 +33,13 @@ struct PainterSkia::Impl {
     {
     }
 
-    SkCanvas* canvas() const
+    template<typename Callback>
+    void with_canvas(Callback&& callback)
     {
-        return &painting_surface->canvas();
+        painting_surface->lock_context();
+        auto& canvas = painting_surface->canvas();
+        callback(canvas);
+        painting_surface->unlock_context();
     }
 };
 
@@ -122,14 +126,18 @@ void PainterSkia::clear_rect(Gfx::FloatRect const& rect, Gfx::Color color)
     SkPaint paint;
     paint.setColor(to_skia_color(color));
     paint.setBlendMode(SkBlendMode::kClear);
-    impl().canvas()->drawRect(to_skia_rect(rect), paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawRect(to_skia_rect(rect), paint);
+    });
 }
 
 void PainterSkia::fill_rect(Gfx::FloatRect const& rect, Color color)
 {
     SkPaint paint;
     paint.setColor(to_skia_color(color));
-    impl().canvas()->drawRect(to_skia_rect(rect), paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawRect(to_skia_rect(rect), paint);
+    });
 }
 
 void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, ReadonlySpan<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@@ -139,13 +147,15 @@ void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitm
     paint.setAlpha(static_cast<u8>(global_alpha * 255));
     paint.setBlender(to_skia_blender(compositing_and_blending_operator));
 
-    impl().canvas()->drawImageRect(
-        src_bitmap.sk_image(),
-        to_skia_rect(src_rect),
-        to_skia_rect(dst_rect),
-        to_skia_sampling_options(scaling_mode),
-        &paint,
-        SkCanvas::kStrict_SrcRectConstraint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawImageRect(
+            src_bitmap.sk_image(),
+            to_skia_rect(src_rect),
+            to_skia_rect(dst_rect),
+            to_skia_sampling_options(scaling_mode),
+            &paint,
+            SkCanvas::kStrict_SrcRectConstraint);
+    });
 }
 
 void PainterSkia::set_transform(Gfx::AffineTransform const& transform)
@@ -155,7 +165,9 @@ void PainterSkia::set_transform(Gfx::AffineTransform const& transform)
         transform.b(), transform.d(), transform.f(),
         0, 0, 1);
 
-    impl().canvas()->setMatrix(matrix);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.setMatrix(matrix);
+    });
 }
 
 void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness)
@@ -170,7 +182,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
     paint.setStrokeWidth(thickness);
     paint.setColor(to_skia_color(color));
     auto sk_path = to_skia_path(path);
-    impl().canvas()->drawPath(sk_path, paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawPath(sk_path, paint);
+    });
 }
 
 void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@@ -187,7 +201,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
     paint.setColor(to_skia_color(color));
     paint.setBlender(to_skia_blender(compositing_and_blending_operator));
     auto sk_path = to_skia_path(path);
-    impl().canvas()->drawPath(sk_path, paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawPath(sk_path, paint);
+    });
 }
 
 void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> filters, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@@ -204,7 +220,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& pain
     paint.setStyle(SkPaint::Style::kStroke_Style);
     paint.setStrokeWidth(thickness);
     paint.setBlender(to_skia_blender(compositing_and_blending_operator));
-    impl().canvas()->drawPath(sk_path, paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawPath(sk_path, paint);
+    });
 }
 
 void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> filters, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle const& cap_style, Gfx::Path::JoinStyle const& join_style)
@@ -223,7 +241,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& pain
     paint.setStrokeCap(to_skia_cap(cap_style));
     paint.setStrokeJoin(to_skia_join(join_style));
     paint.setBlender(to_skia_blender(compositing_and_blending_operator));
-    impl().canvas()->drawPath(sk_path, paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawPath(sk_path, paint);
+    });
 }
 
 void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule)
@@ -233,7 +253,9 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::Windin
     paint.setColor(to_skia_color(color));
     auto sk_path = to_skia_path(path);
     sk_path.setFillType(to_skia_path_fill_type(winding_rule));
-    impl().canvas()->drawPath(sk_path, paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawPath(sk_path, paint);
+    });
 }
 
 void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@@ -245,7 +267,9 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::Windin
     paint.setBlender(to_skia_blender(compositing_and_blending_operator));
     auto sk_path = to_skia_path(path);
     sk_path.setFillType(to_skia_path_fill_type(winding_rule));
-    impl().canvas()->drawPath(sk_path, paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawPath(sk_path, paint);
+    });
 }
 
 void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::WindingRule winding_rule)
@@ -257,24 +281,32 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_
     float alpha = paint.getAlphaf();
     paint.setAlphaf(alpha * global_alpha);
     paint.setBlender(to_skia_blender(compositing_and_blending_operator));
-    impl().canvas()->drawPath(sk_path, paint);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.drawPath(sk_path, paint);
+    });
 }
 
 void PainterSkia::save()
 {
-    impl().canvas()->save();
+    impl().with_canvas([&](auto& canvas) {
+        canvas.save();
+    });
 }
 
 void PainterSkia::restore()
 {
-    impl().canvas()->restore();
+    impl().with_canvas([&](auto& canvas) {
+        canvas.restore();
+    });
 }
 
 void PainterSkia::clip(Gfx::Path const& path, Gfx::WindingRule winding_rule)
 {
     auto sk_path = to_skia_path(path);
     sk_path.setFillType(to_skia_path_fill_type(winding_rule));
-    impl().canvas()->clipPath(sk_path, SkClipOp::kIntersect, true);
+    impl().with_canvas([&](auto& canvas) {
+        canvas.clipPath(sk_path, SkClipOp::kIntersect, true);
+    });
 }
 
 }

+ 19 - 4
Libraries/LibGfx/PaintingSurface.cpp

@@ -21,6 +21,7 @@
 namespace Gfx {
 
 struct PaintingSurface::Impl {
+    RefPtr<SkiaBackendContext> context;
     IntSize size;
     sk_sp<SkSurface> surface;
     RefPtr<Bitmap> bitmap;
@@ -36,12 +37,12 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::create_with_size(RefPtr<SkiaBack
         auto bitmap = Bitmap::create(color_type, alpha_type, size).value();
         auto surface = SkSurfaces::WrapPixels(image_info, bitmap->begin(), bitmap->pitch());
         VERIFY(surface);
-        return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, bitmap)));
+        return adopt_ref(*new PaintingSurface(make<Impl>(context, size, surface, bitmap)));
     }
 
     auto surface = SkSurfaces::RenderTarget(context->sk_context(), skgpu::Budgeted::kNo, image_info);
     VERIFY(surface);
-    return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, nullptr)));
+    return adopt_ref(*new PaintingSurface(make<Impl>(context, size, surface, nullptr)));
 }
 
 NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_bitmap(Bitmap& bitmap)
@@ -51,7 +52,7 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_bitmap(Bitmap& bitmap)
     auto size = bitmap.size();
     auto image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, alpha_type, SkColorSpace::MakeSRGB());
     auto surface = SkSurfaces::WrapPixels(image_info, bitmap.begin(), bitmap.pitch());
-    return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, bitmap)));
+    return adopt_ref(*new PaintingSurface(make<Impl>(RefPtr<SkiaBackendContext> {}, size, surface, bitmap)));
 }
 
 #ifdef AK_OS_MACOS
@@ -75,7 +76,7 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_iosurface(Core::IOSurfaceHa
         VERIFY_NOT_REACHED();
     }
     auto surface = SkSurfaces::WrapBackendRenderTarget(context->sk_context(), backend_render_target, sk_origin, kBGRA_8888_SkColorType, nullptr, nullptr);
-    return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, nullptr)));
+    return adopt_ref(*new PaintingSurface(make<Impl>(context, size, surface, nullptr)));
 }
 #endif
 
@@ -141,4 +142,18 @@ void PaintingSurface::flush()
         on_flush(*this);
 }
 
+void PaintingSurface::lock_context() const
+{
+    auto& context = m_impl->context;
+    if (context)
+        context->lock();
+}
+
+void PaintingSurface::unlock_context() const
+{
+    auto& context = m_impl->context;
+    if (context)
+        context->unlock();
+}
+
 }

+ 3 - 0
Libraries/LibGfx/PaintingSurface.h

@@ -57,6 +57,9 @@ public:
 
     ~PaintingSurface();
 
+    void lock_context() const;
+    void unlock_context() const;
+
 private:
     struct Impl;
 

+ 7 - 0
Libraries/LibGfx/SkiaBackendContext.h

@@ -8,6 +8,7 @@
 
 #include <AK/Noncopyable.h>
 #include <AK/RefCounted.h>
+#include <LibThreading/Mutex.h>
 
 #ifdef USE_VULKAN
 #    include <LibGfx/VulkanContext.h>
@@ -44,6 +45,12 @@ public:
     virtual GrDirectContext* sk_context() const = 0;
 
     virtual MetalContext& metal_context() = 0;
+
+    void lock() { m_mutex.lock(); }
+    void unlock() { m_mutex.unlock(); }
+
+private:
+    Threading::Mutex m_mutex;
 };
 
 }

+ 27 - 16
Libraries/LibJS/AST.cpp

@@ -225,24 +225,35 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation
     auto& realm = *vm.current_realm();
 
     auto property_key_or_private_name = TRY(class_key_to_property_name(vm, *m_key, property_key));
-    GC::Ptr<ECMAScriptFunctionObject> initializer;
+    Variant<GC::Ref<ECMAScriptFunctionObject>, Value, Empty> initializer;
     if (m_initializer) {
-        auto copy_initializer = m_initializer;
-        auto name = property_key_or_private_name.visit(
-            [&](PropertyKey const& property_key) -> String {
-                return property_key.is_number() ? property_key.to_string() : property_key.to_string_or_symbol().to_display_string();
-            },
-            [&](PrivateName const& private_name) -> String {
-                return private_name.description.to_string();
-            });
+        if (auto const* literal = as_if<NumericLiteral>(*m_initializer)) {
+            initializer = literal->value();
+        } else if (auto const* literal = as_if<BooleanLiteral>(*m_initializer)) {
+            initializer = literal->value();
+        } else if (auto const* literal = as_if<NullLiteral>(*m_initializer)) {
+            initializer = literal->value();
+        } else if (auto const* literal = as_if<StringLiteral>(*m_initializer)) {
+            initializer = Value(PrimitiveString::create(vm, literal->value()));
+        } else {
+            auto copy_initializer = m_initializer;
+            auto name = property_key_or_private_name.visit(
+                [&](PropertyKey const& property_key) -> String {
+                    return property_key.is_number() ? property_key.to_string() : property_key.to_string_or_symbol().to_display_string();
+                },
+                [&](PrivateName const& private_name) -> String {
+                    return private_name.description.to_string();
+                });
 
-        // FIXME: A potential optimization is not creating the functions here since these are never directly accessible.
-        auto function_code = create_ast_node<ClassFieldInitializerStatement>(m_initializer->source_range(), copy_initializer.release_nonnull(), name);
-        FunctionParsingInsights parsing_insights;
-        parsing_insights.uses_this_from_environment = true;
-        parsing_insights.uses_this = true;
-        initializer = ECMAScriptFunctionObject::create(realm, "field"_string, ByteString::empty(), *function_code, FunctionParameters::empty(), 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, parsing_insights, false, property_key_or_private_name);
-        initializer->make_method(target);
+            // FIXME: A potential optimization is not creating the functions here since these are never directly accessible.
+            auto function_code = create_ast_node<ClassFieldInitializerStatement>(m_initializer->source_range(), copy_initializer.release_nonnull(), name);
+            FunctionParsingInsights parsing_insights;
+            parsing_insights.uses_this_from_environment = true;
+            parsing_insights.uses_this = true;
+            auto function = ECMAScriptFunctionObject::create(realm, "field"_string, ByteString::empty(), *function_code, FunctionParameters::empty(), 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, parsing_insights, false, property_key_or_private_name);
+            function->make_method(target);
+            initializer = function;
+        }
     }
 
     return ClassValue {

+ 16 - 0
Libraries/LibJS/AST.h

@@ -94,6 +94,8 @@ public:
     virtual bool is_object_expression() const { return false; }
     virtual bool is_numeric_literal() const { return false; }
     virtual bool is_string_literal() const { return false; }
+    virtual bool is_boolean_literal() const { return false; }
+    virtual bool is_null_literal() const { return false; }
     virtual bool is_update_expression() const { return false; }
     virtual bool is_call_expression() const { return false; }
     virtual bool is_labelled_statement() const { return false; }
@@ -1214,6 +1216,8 @@ public:
     virtual Value value() const override { return Value(m_value); }
 
 private:
+    virtual bool is_boolean_literal() const override { return true; }
+
     bool m_value { false };
 };
 
@@ -1281,6 +1285,9 @@ public:
     virtual Bytecode::CodeGenerationErrorOr<Optional<Bytecode::ScopedOperand>> generate_bytecode(Bytecode::Generator&, Optional<Bytecode::ScopedOperand> preferred_dst = {}) const override;
 
     virtual Value value() const override { return js_null(); }
+
+private:
+    virtual bool is_null_literal() const override { return true; }
 };
 
 class RegExpLiteral final : public Expression {
@@ -2267,6 +2274,15 @@ inline bool ASTNode::fast_is<ObjectExpression>() const { return is_object_expres
 template<>
 inline bool ASTNode::fast_is<ImportCall>() const { return is_import_call(); }
 
+template<>
+inline bool ASTNode::fast_is<NumericLiteral>() const { return is_numeric_literal(); }
+
+template<>
+inline bool ASTNode::fast_is<BooleanLiteral>() const { return is_boolean_literal(); }
+
+template<>
+inline bool ASTNode::fast_is<NullLiteral>() const { return is_null_literal(); }
+
 template<>
 inline bool ASTNode::fast_is<StringLiteral>() const { return is_string_literal(); }
 

+ 2 - 2
Libraries/LibJS/Runtime/ClassFieldDefinition.h

@@ -17,8 +17,8 @@ using ClassElementName = Variant<PropertyKey, PrivateName>;
 
 // 6.2.10 The ClassFieldDefinition Record Specification Type, https://tc39.es/ecma262/#sec-classfielddefinition-record-specification-type
 struct ClassFieldDefinition {
-    ClassElementName name;                         // [[Name]]
-    GC::Ptr<ECMAScriptFunctionObject> initializer; // [[Initializer]]
+    ClassElementName name;                                                // [[Name]]
+    Variant<GC::Ref<ECMAScriptFunctionObject>, Value, Empty> initializer; // [[Initializer]]
 };
 
 }

+ 8 - 1
Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp

@@ -553,7 +553,14 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
     visitor.visit(m_bytecode_executable);
 
     for (auto& field : m_fields) {
-        visitor.visit(field.initializer);
+        field.initializer.visit(
+            [&visitor](GC::Ref<ECMAScriptFunctionObject>& initializer) {
+                visitor.visit(initializer);
+            },
+            [&visitor](Value initializer) {
+                visitor.visit(initializer);
+            },
+            [](Empty) {});
         if (auto* property_key_ptr = field.name.get_pointer<PropertyKey>(); property_key_ptr && property_key_ptr->is_symbol())
             visitor.visit(property_key_ptr->as_symbol());
     }

+ 8 - 3
Libraries/LibJS/Runtime/Object.cpp

@@ -669,9 +669,14 @@ ThrowCompletionOr<void> Object::define_field(ClassFieldDefinition const& field)
     auto init_value = js_undefined();
 
     // 3. If initializer is not empty, then
-    if (initializer) {
-        // a. Let initValue be ? Call(initializer, receiver).
-        init_value = TRY(call(vm, initializer, this));
+    if (!initializer.has<Empty>()) {
+        // OPTIMIZATION: If the initializer is a value (from a literal), we can skip the call.
+        if (auto const* initializer_value = initializer.get_pointer<Value>()) {
+            init_value = *initializer_value;
+        } else {
+            // a. Let initValue be ? Call(initializer, receiver).
+            init_value = TRY(call(vm, *initializer.get<GC::Ref<ECMAScriptFunctionObject>>(), this));
+        }
     }
     // 4. Else, let initValue be undefined.
 

+ 1 - 2
Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.cpp

@@ -94,8 +94,7 @@ Optional<Gfx::ImageCursor> CursorStyleValue::make_image_cursor(Layout::NodeWithS
         case DisplayListPlayerType::SkiaCPU: {
             auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap);
             Painting::DisplayListPlayerSkia display_list_player;
-            display_list_player.set_surface(painting_surface);
-            display_list_player.execute(*display_list);
+            display_list_player.execute(*display_list, painting_surface);
             break;
         }
         }

+ 1 - 2
Libraries/LibWeb/HTML/RenderingThread.cpp

@@ -49,8 +49,7 @@ void RenderingThread::rendering_thread_loop()
             break;
         }
 
-        m_skia_player->set_surface(task->painting_surface);
-        m_skia_player->execute(*task->display_list);
+        m_skia_player->execute(*task->display_list, task->painting_surface);
         m_main_thread_event_loop.deferred_invoke([callback = move(task->callback)] {
             callback();
         });

+ 20 - 2
Libraries/LibWeb/Painting/DisplayList.cpp

@@ -35,13 +35,31 @@ static bool command_is_clip_or_mask(Command const& command)
         });
 }
 
-void DisplayListPlayer::execute(DisplayList& display_list)
+void DisplayListPlayer::execute(DisplayList& display_list, RefPtr<Gfx::PaintingSurface> surface)
 {
+    if (surface) {
+        surface->lock_context();
+    }
+    execute_impl(display_list, surface);
+    if (surface) {
+        surface->unlock_context();
+    }
+}
+
+void DisplayListPlayer::execute_impl(DisplayList& display_list, RefPtr<Gfx::PaintingSurface> surface)
+{
+    if (surface)
+        m_surfaces.append(*surface);
+    ScopeGuard guard = [&surfaces = m_surfaces, pop_surface_from_stack = !!surface] {
+        if (pop_surface_from_stack)
+            (void)surfaces.take_last();
+    };
+
     auto const& commands = display_list.commands();
     auto const& scroll_state = display_list.scroll_state();
     auto device_pixels_per_css_pixel = display_list.device_pixels_per_css_pixel();
 
-    VERIFY(m_surface);
+    VERIFY(!m_surfaces.is_empty());
 
     for (size_t command_index = 0; command_index < commands.size(); command_index++) {
         auto scroll_frame_id = commands[command_index].scroll_frame_id;

+ 4 - 4
Libraries/LibWeb/Painting/DisplayList.h

@@ -26,11 +26,11 @@ class DisplayListPlayer {
 public:
     virtual ~DisplayListPlayer() = default;
 
-    void execute(DisplayList&);
-    void set_surface(NonnullRefPtr<Gfx::PaintingSurface> surface) { m_surface = surface; }
+    void execute(DisplayList&, RefPtr<Gfx::PaintingSurface>);
 
 protected:
-    Gfx::PaintingSurface& surface() const { return *m_surface; }
+    Gfx::PaintingSurface& surface() const { return m_surfaces.last(); }
+    void execute_impl(DisplayList&, RefPtr<Gfx::PaintingSurface>);
 
 private:
     virtual void flush() = 0;
@@ -74,7 +74,7 @@ private:
     virtual void apply_mask_bitmap(ApplyMaskBitmap const&) = 0;
     virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0;
 
-    RefPtr<Gfx::PaintingSurface> m_surface;
+    Vector<NonnullRefPtr<Gfx::PaintingSurface>, 1> m_surfaces;
 };
 
 class DisplayList : public AtomicRefCounted<DisplayList> {

+ 2 - 5
Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp

@@ -972,10 +972,7 @@ void DisplayListPlayerSkia::add_mask(AddMask const& command)
 
     auto mask_surface = Gfx::PaintingSurface::create_with_size(m_context, rect.size(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
 
-    NonnullRefPtr old_surface = surface();
-    set_surface(mask_surface);
-    execute(*command.display_list);
-    set_surface(old_surface);
+    execute_impl(*command.display_list, mask_surface);
 
     SkMatrix mask_matrix;
     mask_matrix.setTranslate(rect.x(), rect.y());
@@ -988,7 +985,7 @@ void DisplayListPlayerSkia::paint_nested_display_list(PaintNestedDisplayList con
 {
     auto& canvas = surface().canvas();
     canvas.translate(command.rect.x(), command.rect.y());
-    execute(*command.display_list);
+    execute_impl(*command.display_list, {});
 }
 
 void DisplayListPlayerSkia::paint_scrollbar(PaintScrollBar const& command)

+ 1 - 2
Libraries/LibWeb/Painting/SVGMaskable.cpp

@@ -100,8 +100,7 @@ RefPtr<Gfx::ImmutableBitmap> SVGMaskable::calculate_mask_of_svg(PaintContext& co
         StackingContext::paint_svg(paint_context, paintable, PaintPhase::Foreground);
         auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(*mask_bitmap);
         DisplayListPlayerSkia display_list_player;
-        display_list_player.set_surface(painting_surface);
-        display_list_player.execute(display_list);
+        display_list_player.execute(display_list, painting_surface);
         return mask_bitmap;
     };
     RefPtr<Gfx::Bitmap> mask_bitmap = {};

+ 1 - 2
Libraries/LibWeb/SVG/SVGDecodedImageData.cpp

@@ -106,8 +106,7 @@ RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
     case DisplayListPlayerType::SkiaCPU: {
         auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(*bitmap);
         Painting::DisplayListPlayerSkia display_list_player;
-        display_list_player.set_surface(painting_surface);
-        display_list_player.execute(*display_list);
+        display_list_player.execute(*display_list, painting_surface);
         break;
     }
     default:

+ 7 - 0
UI/AppKit/Interface/LadybirdWebView.mm

@@ -1684,6 +1684,13 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
     m_modifier_flags = event.modifierFlags;
 }
 
+- (void)cursorUpdate:(NSEvent*)event
+{
+    // The NSApp will override the custom cursor set from on_cursor_change when the view hierarchy changes in some way
+    // (such as when we show self.status_label on link hover). Overriding this method with an empty implementation will
+    // prevent this from happening. See: https://stackoverflow.com/a/20197686
+}
+
 #pragma mark - NSDraggingDestination
 
 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)event