Browse Source

Ladybird/AppKit: Port TaskManager window to Swift

This is just a direct port of the Objective-C++ code to Swift 6.
A future patch should probably update it to actually use SwiftUI.
Andrew Kaster 1 năm trước cách đây
mục cha
commit
68ce5f8290

+ 1 - 1
.github/actions/setup/action.yml

@@ -50,7 +50,7 @@ runs:
       shell: bash
       run: |
         set -e
-        sudo xcode-select --switch /Applications/Xcode_15.4.app
+        sudo xcode-select --switch /Applications/Xcode_16.0.app
         brew update
         brew install autoconf autoconf-archive automake coreutils bash ffmpeg ninja wabt ccache unzip qt llvm@18 nasm
 

+ 10 - 0
.github/workflows/lagom-template.yml

@@ -138,6 +138,16 @@ jobs:
         working-directory: ${{ github.workspace }}/Build
         run: cmake --build .
 
+      - name: Enable the AppKit chrome with Swift files
+        if: ${{ inputs.os_name == 'macOS' && inputs.fuzzer == 'NO_FUZZ' }}
+        working-directory: ${{ github.workspace }}
+        run: cmake -B Build -DENABLE_QT=OFF -DENABLE_SWIFT=ON
+
+      - name: Build the AppKit chrome with Swift files
+        if: ${{ inputs.os_name == 'macOS' && inputs.fuzzer == 'NO_FUZZ' }}
+        working-directory: ${{ github.workspace }}/Build
+        run: cmake --build .
+
       - name: Save Caches
         uses: ./.github/actions/cache-save
         with:

+ 6 - 0
CMakeLists.txt

@@ -36,15 +36,20 @@ include(lagom_install_options)
 if (ENABLE_ADDRESS_SANITIZER)
     add_cxx_compile_options(-fsanitize=address -fno-omit-frame-pointer)
     add_cxx_link_options(-fsanitize=address)
+    add_swift_compile_options(-sanitize=address)
+    add_swift_link_options(-sanitize=address)
 endif()
 
 if (ENABLE_MEMORY_SANITIZER)
     add_cxx_compile_options(-fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer)
     add_cxx_link_options(-fsanitize=memory -fsanitize-memory-track-origins)
+    add_swift_compile_options(-sanitize=memory)
+    add_swift_link_options(-sanitize=memory)
 endif()
 
 if (ENABLE_UNDEFINED_SANITIZER)
     add_cxx_compile_options(-fsanitize=undefined -fno-omit-frame-pointer)
+    add_swift_compile_options(-sanitize=address)
     if (UNDEFINED_BEHAVIOR_IS_FATAL)
         add_cxx_compile_options(-fno-sanitize-recover=undefined)
     endif()
@@ -52,6 +57,7 @@ if (ENABLE_UNDEFINED_SANITIZER)
         add_cxx_compile_options(-fno-sanitize=function)
     endif()
     add_cxx_link_options(-fsanitize=undefined)
+    add_swift_link_options(-sanitize=address)
 endif()
 
 if (HAIKU)

+ 11 - 2
Ladybird/AppKit/Application/ApplicationDelegate.mm

@@ -11,7 +11,16 @@
 #import <UI/LadybirdWebView.h>
 #import <UI/Tab.h>
 #import <UI/TabController.h>
-#import <UI/TaskManagerController.h>
+
+#if defined(LADYBIRD_USE_SWIFT)
+// FIXME: Report this codegen error to Apple
+#    define StyleMask NSWindowStyleMask
+#    import <Ladybird-Swift.h>
+#    undef StyleMask
+#else
+#    import <UI/TaskManagerController.h>
+#endif
+
 #import <Utilities/Conversions.h>
 
 #if !__has_feature(objc_arc)
@@ -237,7 +246,7 @@
         return;
     }
 
-    self.task_manager_controller = [[TaskManagerController alloc] init:self];
+    self.task_manager_controller = [[TaskManagerController alloc] initWithDelegate:self];
     [self.task_manager_controller showWindow:nil];
 }
 

+ 46 - 9
Ladybird/AppKit/CMakeLists.txt

@@ -1,6 +1,5 @@
-add_executable(ladybird MACOSX_BUNDLE
+add_library(ladybird_impl STATIC
     ${LADYBIRD_SOURCES}
-    main.mm
     Application/Application.mm
     Application/ApplicationBridge.cpp
     Application/ApplicationDelegate.mm
@@ -14,14 +13,52 @@ add_executable(ladybird MACOSX_BUNDLE
     UI/SearchPanel.mm
     UI/Tab.mm
     UI/TabController.mm
-    UI/TaskManager.mm
-    UI/TaskManagerController.mm
     Utilities/Conversions.mm
 )
-target_include_directories(ladybird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
-target_link_libraries(ladybird PRIVATE "-framework Cocoa -framework UniformTypeIdentifiers" LibUnicode)
-target_compile_options(ladybird PRIVATE
-        -fobjc-arc
-        -Wno-deprecated-anon-enum-enum-conversion # Required for CGImageCreate
+target_include_directories(ladybird_impl PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+
+target_compile_options(ladybird_impl PRIVATE "SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xcc -std=c++23 -cxx-interoperability-mode=default>")
+target_compile_options(ladybird_impl PUBLIC
+        $<$<COMPILE_LANGUAGE:CXX>:-fobjc-arc>
+        $<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-anon-enum-enum-conversion> # Required for CGImageCreate
+)
+target_compile_features(ladybird_impl PUBLIC cxx_std_23)
+
+if (ENABLE_SWIFT)
+    enable_language(Swift)
+    if (CMAKE_Swift_COMPILER_VERSION VERSION_LESS 6.0)
+        message(FATAL_ERROR
+            "Swift 6.0 or newer is required to parse AK C++ headers in C++23 mode"
+        )
+    endif()
+    if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
+        message(FATAL_ERROR
+            "Swift files must use Clang that was bundled with swiftc"
+        )
+    endif()
+    include(../cmake/GenerateSwiftHeader.cmake)
+
+    target_sources(ladybird_impl PRIVATE
+        UI/TaskManager.swift
+        UI/TaskManagerController.swift
+    )
+    target_compile_definitions(ladybird_impl PUBLIC LADYBIRD_USE_SWIFT)
+    set_target_properties(ladybird_impl PROPERTIES Swift_MODULE_NAME "SwiftLadybird")
+
+    get_target_property(LADYBIRD_NATIVE_DIRS ladybird_impl INCLUDE_DIRECTORIES)
+    _swift_generate_cxx_header(ladybird_impl "Ladybird-Swift.h"
+        SEARCH_PATHS ${LADYBIRD_NATIVE_DIRS}
+    )
+else()
+    target_sources(ladybird_impl PRIVATE
+        UI/TaskManager.mm
+        UI/TaskManagerController.mm
+    )
+endif()
+
+add_executable(ladybird MACOSX_BUNDLE
+   main.mm
 )
+target_link_libraries(ladybird PRIVATE "-framework Cocoa -framework UniformTypeIdentifiers" LibUnicode ladybird_impl)
+
 create_ladybird_bundle(ladybird)

+ 64 - 0
Ladybird/AppKit/UI/TaskManager.swift

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+import Foundation
+import Ladybird.WebView
+import Ladybird.WebViewApplication
+import SwiftUI
+
+public class TaskManager: NSWindow {
+
+  private let WINDOW_WIDTH: CGFloat = 600
+  private let WINDOW_HEIGHT: CGFloat = 400
+
+  var web_view: LadybirdWebView
+  private var timer: Timer?
+
+  init() {
+    let tab_rect = NSApplication.shared.keyWindow!.frame
+    let position_x = tab_rect.origin.x + (tab_rect.size.width - WINDOW_WIDTH) / 2
+    let position_y = tab_rect.origin.y + (tab_rect.size.height - WINDOW_HEIGHT) / 2
+
+    let window_rect = NSMakeRect(position_x, position_y, WINDOW_WIDTH, WINDOW_HEIGHT)
+    let style_mask = NSWindow.StyleMask.init(arrayLiteral: [
+      NSWindow.StyleMask.titled, NSWindow.StyleMask.closable, NSWindow.StyleMask.miniaturizable,
+      NSWindow.StyleMask.resizable,
+    ])
+
+    self.web_view = LadybirdWebView.init(nil)
+
+    super.init(
+      contentRect: window_rect, styleMask: style_mask, backing: NSWindow.BackingStoreType.buffered,
+      defer: false)
+
+    self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
+      if let strong_self = self {
+        strong_self.updateStatistics()
+      }
+    }
+
+    self.web_view.postsBoundsChangedNotifications = true
+    let scroll_view = NSScrollView()
+    scroll_view.hasVerticalScroller = true
+    scroll_view.hasHorizontalScroller = true
+    scroll_view.lineScroll = 24
+
+    scroll_view.contentView = self.web_view
+    scroll_view.documentView = NSView()
+
+    self.contentView = scroll_view
+    self.title = "Task Manager"
+    self.setIsVisible(true)
+
+    self.updateStatistics()
+  }
+
+  func updateStatistics() {
+    WebView.Application.the().update_process_statistics();
+    self.web_view.loadHTML(WebView.Application.the().generate_process_statistics_html().__bytes_as_string_viewUnsafe());
+  }
+}

+ 1 - 1
Ladybird/AppKit/UI/TaskManagerController.h

@@ -16,6 +16,6 @@
 
 @interface TaskManagerController : NSWindowController
 
-- (instancetype)init:(id<TaskManagerDelegate>)delegate;
+- (instancetype)initWithDelegate:(id<TaskManagerDelegate>)delegate;
 
 @end

+ 1 - 1
Ladybird/AppKit/UI/TaskManagerController.mm

@@ -20,7 +20,7 @@
 
 @implementation TaskManagerController
 
-- (instancetype)init:(id<TaskManagerDelegate>)delegate
+- (instancetype)initWithDelegate:(id<TaskManagerDelegate>)delegate
 {
     if (self = [super init]) {
         self.delegate = delegate;

+ 50 - 0
Ladybird/AppKit/UI/TaskManagerController.swift

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+import Foundation
+import SwiftUI
+
+@objc
+public protocol TaskManagerDelegate where Self: NSObject {
+  func onTaskManagerClosed()
+}
+
+public class TaskManagerController: NSWindowController, NSWindowDelegate {
+
+  private weak var delegate: TaskManagerDelegate?
+
+  @objc
+  public convenience init(delegate: TaskManagerDelegate) {
+    self.init()
+    self.delegate = delegate
+  }
+
+  @IBAction public override func showWindow(_ sender: Any?) {
+    self.window = TaskManager.init()
+    self.window!.delegate = self
+    self.window!.makeKeyAndOrderFront(sender)
+  }
+
+  public func windowWillClose(_ sender: Notification) {
+    self.delegate?.onTaskManagerClosed()
+  }
+
+  public func windowDidResize(_ sender: Notification) {
+    guard self.window != nil else { return }
+    if !self.window!.inLiveResize {
+      self.taskManager().web_view.handleResize()
+    }
+  }
+
+  public func windowDidChangeBackingProperties(_ sender: Notification) {
+    self.taskManager().web_view.handleDevicePixelRatioChange()
+  }
+
+  private func taskManager() -> TaskManager {
+    return self.window as! TaskManager
+  }
+}

+ 14 - 0
Ladybird/AppKit/module.modulemap

@@ -0,0 +1,14 @@
+module Ladybird [system] {
+    requires cplusplus
+    requires objc_arc
+
+    explicit module WebView {
+        header "UI/LadybirdWebView.h"
+        export *
+    }
+
+    explicit module WebViewApplication {
+        header "../../Userland/Libraries/LibWebView/Application.h"
+        export *
+    }
+}

+ 85 - 0
Ladybird/cmake/GenerateSwiftHeader.cmake

@@ -0,0 +1,85 @@
+# This source file is part of the Swift open source project
+#
+# Copyright (c) 2023 Apple Inc. and the Swift project authors.
+# Licensed under Apache License v2.0 with Runtime Library Exception
+#
+# See https://swift.org/LICENSE.txt for license information
+
+
+# Generate the bridging header from Swift to C++
+#
+# target: the name of the target to generate headers for.
+#         This target must build swift source files.
+# header: the name of the header file to generate.
+#
+# NOTE: This logic will eventually be unstreamed into CMake.
+function(_swift_generate_cxx_header target header)
+  if(NOT TARGET ${target})
+    message(FATAL_ERROR "Target ${target} not defined.")
+  endif()
+
+  if(NOT DEFINED CMAKE_Swift_COMPILER)
+    message(WARNING "Swift not enabled in project. Cannot generate headers for Swift files.")
+    return()
+  endif()
+
+  cmake_parse_arguments(ARG "" "" "SEARCH_PATHS;MODULE_NAME;CXX_STD_VERSION" ${ARGN})
+
+  if(NOT ARG_MODULE_NAME)
+    set(target_module_name $<TARGET_PROPERTY:${target},Swift_MODULE_NAME>)
+    set(ARG_MODULE_NAME $<IF:$<BOOL:${target_module_name}>,${target_module_name},${target}>)
+  endif()
+
+  if(ARG_SEARCH_PATHS)
+    list(TRANSFORM ARG_SEARCH_PATHS PREPEND "-I")
+  endif()
+
+  if(APPLE)
+    set(SDK_FLAGS "-sdk" "${CMAKE_OSX_SYSROOT}")
+  elseif(WIN32)
+    set(SDK_FLAGS "-sdk" "$ENV{SDKROOT}")
+  elseif(DEFINED ${CMAKE_SYSROOT})
+    set(SDK_FLAGS "-sdk" "${CMAKE_SYSROOT}")
+  endif()
+
+  cmake_path(APPEND CMAKE_CURRENT_BINARY_DIR include
+    OUTPUT_VARIABLE base_path)
+
+  cmake_path(APPEND base_path ${header}
+    OUTPUT_VARIABLE header_path)
+
+  if (NOT ARG_CXX_STD_VERSION)
+    set(ARG_CXX_STD_VERSION "14")
+    get_target_property(CxxFeatures ${target} COMPILE_FEATURES)
+    list(FILTER CxxFeatures INCLUDE REGEX "cxx_std_[0-9]+$")
+    if (NOT "${CxxFeatures}" STREQUAL "")
+        string(SUBSTRING ${CxxFeatures} 8 2 ARG_CXX_STD_VERSION)
+    endif()
+  endif()
+
+  set(CXX_STD_FLAGS -Xcc -std=c++${ARG_CXX_STD_VERSION})
+
+  set(_AllSources $<TARGET_PROPERTY:${target},SOURCES>)
+  set(_SwiftSources $<FILTER:${_AllSources},INCLUDE,\\.swift$>)
+  add_custom_command(OUTPUT ${header_path}
+    DEPENDS ${_SwiftSources}
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    COMMAND
+      ${CMAKE_Swift_COMPILER} -frontend -typecheck
+      ${ARG_SEARCH_PATHS}
+      ${_SwiftSources}
+      ${SDK_FLAGS}
+      ${CXX_STD_FLAGS}
+      -module-name "${ARG_MODULE_NAME}"
+      -cxx-interoperability-mode=default
+      -emit-clang-header-path ${header_path}
+    COMMENT
+      "Generating '${header_path}'"
+    COMMAND_EXPAND_LISTS)
+
+  # Added to public interface for dependees to find.
+  target_include_directories(${target} PUBLIC $<BUILD_INTERFACE:${base_path}>)
+  # Added to the target to ensure target rebuilds if header changes and is used
+  # by sources in the target.
+  target_sources(${target} PRIVATE ${header_path})
+endfunction()

+ 16 - 0
Meta/CMake/common_compile_options.cmake

@@ -21,6 +21,22 @@ macro(add_cxx_link_options)
   add_link_options($<$<LINK_LANGUAGE:C,CXX>:${args}>) 
 endmacro()
 
+macro(add_swift_compile_options)
+  set(args "")
+  foreach(arg ${ARGN})
+    string(APPEND args ${arg}$<SEMICOLON>)
+  endforeach()
+  add_compile_options($<$<COMPILE_LANGUAGE:Swift>:${args}>) 
+endmacro()
+
+macro(add_swift_link_options)
+  set(args "")
+  foreach(arg ${ARGN})
+    string(APPEND args ${arg}$<SEMICOLON>)
+  endforeach()
+  add_link_options($<$<LINK_LANGUAGE:Swift>:${args}>) 
+endmacro()
+
 if (MSVC)
     add_cxx_compile_options(/W4)
     # do not warn about unused function

+ 1 - 0
Meta/CMake/common_options.cmake

@@ -28,3 +28,4 @@ serenity_option(ENABLE_CLANG_PLUGINS OFF CACHE BOOL "Enable building with the Cl
 serenity_option(ENABLE_CLANG_PLUGINS_INVALID_FUNCTION_MEMBERS OFF CACHE BOOL "Enable detecting invalid function types as members of GC-allocated objects")
 
 serenity_option(ENABLE_GUI_TARGETS ON CACHE BOOL "Enable building GUI targets")
+serenity_option(ENABLE_SWIFT OFF CACHE BOOL "Enable building Swift files")

+ 7 - 1
Userland/Libraries/LibWebView/Application.h

@@ -10,6 +10,12 @@
 #include <LibWebView/Process.h>
 #include <LibWebView/ProcessManager.h>
 
+#ifdef __swift__
+#    include <swift/bridging>
+#else
+#    define SWIFT_IMMORTAL_REFERENCE
+#endif
+
 namespace WebView {
 
 class Application {
@@ -46,6 +52,6 @@ private:
     Core::EventLoop m_event_loop;
     ProcessManager m_process_manager;
     bool m_in_shutdown { false };
-};
+} SWIFT_IMMORTAL_REFERENCE;
 
 }