Browse Source

Merge branch 'main' into test-patch-lf

VerteDinde 10 months ago
parent
commit
0f403b2468
91 changed files with 448 additions and 175 deletions
  1. 15 3
      .github/actions/build-electron/action.yml
  2. 13 1
      .github/actions/restore-cache-azcopy/action.yml
  3. 60 20
      .github/workflows/build.yml
  4. 1 1
      .github/workflows/linux-publish.yml
  5. 6 0
      .github/workflows/pipeline-electron-build-and-test-and-nan.yml
  6. 8 0
      .github/workflows/pipeline-electron-build-and-test.yml
  7. 9 1
      .github/workflows/pipeline-segment-electron-build.yml
  8. 6 1
      .github/workflows/pipeline-segment-electron-gn-check.yml
  9. 37 11
      .github/workflows/pipeline-segment-electron-test.yml
  10. 1 1
      docs/api/system-preferences.md
  11. 1 1
      docs/tutorial/asar-integrity.md
  12. 1 0
      filenames.auto.gni
  13. 2 1
      lib/utility/api/module-list.ts
  14. 8 0
      lib/worker/init.ts
  15. 0 2
      package.json
  16. 3 3
      script/gen-filenames.ts
  17. 5 5
      script/gn-asar.js
  18. 4 2
      script/lint.js
  19. 2 2
      script/spec-runner.js
  20. 1 2
      shell/app/electron_main_delegate.h
  21. 1 1
      shell/browser/api/electron_api_auto_updater.h
  22. 1 0
      shell/browser/api/electron_api_menu_mac.h
  23. 1 0
      shell/browser/api/electron_api_menu_views.h
  24. 2 0
      shell/browser/api/electron_api_system_preferences_win.cc
  25. 1 0
      shell/browser/api/electron_api_view.cc
  26. 3 0
      shell/browser/api/electron_api_web_contents.h
  27. 2 1
      shell/browser/api/frame_subscriber.h
  28. 2 0
      shell/browser/api/gpu_info_enumerator.h
  29. 1 0
      shell/browser/api/gpuinfo_manager.h
  30. 5 2
      shell/browser/api/message_port.cc
  31. 2 1
      shell/browser/browser_process_impl.h
  32. 1 0
      shell/browser/electron_autofill_driver.h
  33. 2 0
      shell/browser/electron_browser_main_parts.cc
  34. 1 1
      shell/browser/electron_navigation_throttle.h
  35. 2 0
      shell/browser/extensions/api/extension_action/extension_action_api.h
  36. 1 1
      shell/browser/extensions/api/management/electron_management_api_delegate.h
  37. 1 0
      shell/browser/extensions/electron_extensions_browser_api_provider.h
  38. 1 1
      shell/browser/extensions/electron_kiosk_delegate.h
  39. 1 0
      shell/browser/native_window_views.cc
  40. 1 0
      shell/browser/net/asar/asar_file_validator.h
  41. 1 0
      shell/browser/notifications/linux/notification_presenter_linux.h
  42. 1 0
      shell/browser/notifications/mac/notification_presenter_mac.h
  43. 1 0
      shell/browser/notifications/platform_notification_service.cc
  44. 1 0
      shell/browser/notifications/win/notification_presenter_win.h
  45. 5 0
      shell/browser/notifications/win/windows_toast_notification.h
  46. 1 0
      shell/browser/osr/osr_host_display_client.h
  47. 1 0
      shell/browser/osr/osr_render_widget_host_view.cc
  48. 0 1
      shell/browser/osr/osr_render_widget_host_view.h
  49. 1 0
      shell/browser/serial/electron_serial_delegate.h
  50. 1 0
      shell/browser/ui/file_dialog_linux.cc
  51. 1 0
      shell/browser/ui/inspectable_web_contents.cc
  52. 1 0
      shell/browser/ui/message_box_gtk.cc
  53. 1 0
      shell/browser/ui/tray_icon_cocoa.h
  54. 1 0
      shell/browser/ui/views/autofill_popup_view.h
  55. 1 0
      shell/browser/ui/views/menu_model_adapter.h
  56. 1 1
      shell/browser/ui/views/submenu_button.h
  57. 1 1
      shell/browser/ui/views/win_caption_button.h
  58. 1 0
      shell/browser/ui/win/electron_desktop_window_tree_host_win.h
  59. 1 0
      shell/browser/webauthn/electron_authenticator_request_delegate.h
  60. 2 0
      shell/common/gin_helper/function_template.h
  61. 4 3
      shell/common/node_bindings.cc
  62. 1 0
      shell/common/node_bindings_linux.h
  63. 1 0
      shell/common/node_bindings_mac.h
  64. 1 0
      shell/common/node_bindings_win.h
  65. 1 0
      shell/common/platform_util_win.cc
  66. 2 1
      shell/renderer/electron_api_service_impl.h
  67. 1 1
      shell/renderer/electron_renderer_pepper_host_factory.h
  68. 18 12
      shell/renderer/web_worker_observer.cc
  69. 7 1
      shell/services/node/parent_port.cc
  70. 1 0
      shell/utility/electron_content_utility_client.h
  71. 4 4
      spec/api-app-spec.ts
  72. 16 16
      spec/api-autoupdater-darwin-spec.ts
  73. 4 4
      spec/api-context-bridge-spec.ts
  74. 3 3
      spec/api-safe-storage-spec.ts
  75. 3 3
      spec/api-shell-spec.ts
  76. 24 0
      spec/api-utility-process-spec.ts
  77. 2 2
      spec/asar-integrity-spec.ts
  78. 24 1
      spec/chromium-spec.ts
  79. 4 4
      spec/esm-spec.ts
  80. 6 0
      spec/fixtures/api/utility-process/expose-main-process-module.js
  81. 11 0
      spec/fixtures/api/utility-process/non-cloneable.js
  82. 12 0
      spec/fixtures/pages/worker-fetch.html
  83. 7 0
      spec/fixtures/workers/worker_node_fetch.js
  84. 9 12
      spec/get-files.ts
  85. 1 1
      spec/index.js
  86. 6 6
      spec/lib/codesign-helpers.ts
  87. 2 3
      spec/lib/fs-helpers.ts
  88. 34 2
      spec/node-spec.ts
  89. 0 1
      spec/package.json
  90. 0 5
      spec/yarn.lock
  91. 0 22
      yarn.lock

+ 15 - 3
.github/actions/build-electron/action.yml

@@ -23,6 +23,9 @@ inputs:
   upload-to-storage:
     description: 'Upload to storage'
     required: true
+  is-asan:
+    description: 'The ASan Linux build'
+    required: false
 runs:
   using: "composite"
   steps:
@@ -56,7 +59,7 @@ runs:
       run: |
         cd src
         e build electron:electron_dist_zip -j $NUMBER_OF_NINJA_PROCESSES
-        if [ "${{ env.CHECK_DIST_MANIFEST }}" = "true" ]; then
+        if [ "${{ inputs.is-asan }}" != "true" ]; then
           target_os=${{ inputs.target-platform == 'linux' && 'linux' || 'mac'}}
           if [ "${{ inputs.artifact-platform }}" = "mas" ]; then
             target_os="${target_os}_mas"
@@ -181,6 +184,15 @@ runs:
           echo 'Uploading Electron release distribution to GitHub releases'
           script/release/uploaders/upload.py --verbose
         fi
+    - name: Generate Artifact Key
+      shell: bash
+      run: |
+        if [ "${{ inputs.is-asan }}" = "true" ]; then 
+          ARTIFACT_KEY=${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}_asan
+        else
+          ARTIFACT_KEY=${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}
+        fi
+        echo "ARTIFACT_KEY=$ARTIFACT_KEY" >> $GITHUB_ENV
     # The current generated_artifacts_<< artifact.key >> name was taken from CircleCI
     # to ensure we don't break anything, but we may be able to improve that.
     - name: Move all Generated Artifacts to Upload Folder ${{ inputs.step-suffix }}
@@ -189,10 +201,10 @@ runs:
     - name: Upload Generated Artifacts ${{ inputs.step-suffix }}
       uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
       with:
-        name: generated_artifacts_${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}
+        name: generated_artifacts_${{ env.ARTIFACT_KEY }}
         path: ./generated_artifacts_${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}
     - name: Upload Src Artifacts ${{ inputs.step-suffix }}
       uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
       with:
-        name: src_artifacts_${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}
+        name: src_artifacts_${{ env.ARTIFACT_KEY }}
         path: ./src_artifacts_${{ inputs.artifact-platform }}_${{ env.TARGET_ARCH }}

+ 13 - 1
.github/actions/restore-cache-azcopy/action.yml

@@ -4,7 +4,15 @@ runs:
   using: "composite"
   steps:
   - name: Obtain SAS Key
-    uses: actions/cache/restore@v4
+    continue-on-error: true
+    uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9
+    with:
+      path: |
+        sas-token
+      key: sas-key-${{ github.run_number }}-1
+  - name: Obtain SAS Key
+    continue-on-error: true
+    uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9
     with:
       path: |
         sas-token
@@ -20,6 +28,10 @@ runs:
       retry_on: error
       command: |
         sas_token=$(cat sas-token)
+        if [ -z $sas-token ]; then
+          echo "SAS Token not found; exiting src cache download early..."
+          exit 1
+        fi
         azcopy copy \
           "https://${{ env.AZURE_AKS_CACHE_STORAGE_ACCOUNT }}.file.core.windows.net/${{ env.AZURE_AKS_CACHE_SHARE_NAME }}/${{ env.CACHE_PATH }}?$sas_token" $DEPSHASH.tar
   - name: Clean SAS Key

+ 60 - 20
.github/workflows/build.yml

@@ -23,17 +23,22 @@ on:
         description: 'Skip lint check'
         default: false
         required: false
-  # push:
-  # pull_request:
-
+  push:
+    branches:
+      - main
+      - '[1-9][0-9]-x-y'
+  pull_request:
+      
 jobs:
-  changes:
+  setup:
     runs-on: ubuntu-latest
     permissions:
       pull-requests: read
     outputs:
         docs: ${{ steps.filter.outputs.docs }}
         src: ${{ steps.filter.outputs.src }}
+        build-image-sha: ${{ steps.set-output.outputs.build-image-sha }}
+        docs-only: ${{ steps.set-output.outputs.docs-only }}
     steps:
     - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.0.2
     - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@@ -44,37 +49,49 @@ jobs:
             - 'docs/**'
           src:
             - '!docs/**'
+    - name: Set Outputs for Build Image SHA & Docs Only
+      id: set-output
+      run: |
+        if [ -z "${{ inputs.build-image-sha }}" ]; then
+          echo "build-image-sha=cf814a4d2501e8e843caea071a6b70a48e78b855" >> "$GITHUB_OUTPUT"
+        else
+          echo "build-image-sha=${{ inputs.build-image-sha }}" >> "$GITHUB_OUTPUT"
+        fi
+        echo "docs-only=${{ steps.filter.outputs.docs == 'true' && steps.filter.outputs.src == 'false' }}" >> "$GITHUB_OUTPUT"
 
   # Lint Jobs
   lint:
+    needs: setup
     if: ${{ !inputs.skip-lint }}
     uses: ./.github/workflows/pipeline-electron-lint.yml
     with:
-      container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root"}'
+      container: '{"image":"ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}","options":"--user root"}'
     secrets: inherit
 
   # Docs Only Jobs
   docs-only:
-    needs: changes
-    if: ${{ needs.changes.outputs.docs == 'true' && needs.changes.outputs.src == 'false'}}
+    needs: setup
+    if: ${{ needs.setup.outputs.docs-only == 'true' }}
     uses: ./.github/workflows/pipeline-electron-docs-only.yml
     with:
-      container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root"}'
+      container: '{"image":"ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}","options":"--user root"}'
     secrets: inherit
 
   # Checkout Jobs
   checkout-macos:
-    needs: changes
-    if: ${{ needs.changes.outputs.src == 'true' && !inputs.skip-macos}}
+    needs: setup
+    if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-macos}}
     runs-on: aks-linux-large
     container:
-      image: ghcr.io/electron/build:${{ inputs.build-image-sha }}
+      image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
       options: --user root
       volumes:
         - /mnt/cross-instance-cache:/mnt/cross-instance-cache
         - /var/run/sas:/var/run/sas
     env:
       GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
+    outputs:
+      build-image-sha: ${{ needs.setup.outputs.build-image-sha }}
     steps:
     - name: Checkout Electron
       uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
@@ -87,17 +104,19 @@ jobs:
         generate-sas-token: 'true'
 
   checkout-linux:
-    needs: changes
-    if: ${{ needs.changes.outputs.src == 'true' && !inputs.skip-linux}}
+    needs: setup
+    if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-linux}}
     runs-on: aks-linux-large
     container:
-      image: ghcr.io/electron/build:${{ inputs.build-image-sha }}
+      image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
       options: --user root
       volumes:
         - /mnt/cross-instance-cache:/mnt/cross-instance-cache
         - /var/run/sas:/var/run/sas
     env:
       GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
+    outputs:
+      build-image-sha: ${{ needs.setup.outputs.build-image-sha}}
     steps:
     - name: Checkout Electron
       uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
@@ -154,14 +173,35 @@ jobs:
     with:
       build-runs-on: aks-linux-large
       test-runs-on: aks-linux-medium
-      build-container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
-      test-container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root --privileged --init"}'
+      build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
+      test-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init"}'
+      target-platform: linux
+      target-arch: x64
+      is-release: false
+      gn-build-type: testing
+      generate-symbols: false
+      upload-to-storage: '0'
+    secrets: inherit
+
+  linux-x64-asan:
+    permissions:
+      contents: read
+      issues: read
+      pull-requests: read
+    uses: ./.github/workflows/pipeline-electron-build-and-test.yml
+    needs: checkout-linux
+    with:
+      build-runs-on: aks-linux-large
+      test-runs-on: aks-linux-medium
+      build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
+      test-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init"}'
       target-platform: linux
       target-arch: x64
       is-release: false
       gn-build-type: testing
       generate-symbols: false
       upload-to-storage: '0'
+      is-asan: true
     secrets: inherit
   
   linux-arm:
@@ -174,8 +214,8 @@ jobs:
     with:
       build-runs-on: aks-linux-large
       test-runs-on: aks-linux-arm-medium
-      build-container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
-      test-container: '{"image":"ghcr.io/electron/test:arm32v7-${{ inputs.build-image-sha }}","options":"--user root --privileged --init","volumes":["/home/runner/externals:/mnt/runner-externals"]}'
+      build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
+      test-container: '{"image":"ghcr.io/electron/test:arm32v7-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init","volumes":["/home/runner/externals:/mnt/runner-externals"]}'
       target-platform: linux
       target-arch: arm
       is-release: false
@@ -194,8 +234,8 @@ jobs:
     with:
       build-runs-on: aks-linux-large
       test-runs-on: aks-linux-arm-medium
-      build-container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
-      test-container: '{"image":"ghcr.io/electron/test:arm64v8-${{ inputs.build-image-sha }}","options":"--user root --privileged --init"}'
+      build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
+      test-container: '{"image":"ghcr.io/electron/test:arm64v8-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init"}'
       target-platform: linux
       target-arch: arm64
       is-release: false

+ 1 - 1
.github/workflows/linux-publish.yml

@@ -27,7 +27,7 @@ jobs:
         - /mnt/cross-instance-cache:/mnt/cross-instance-cache
         - /var/run/sas:/var/run/sas
     env:
-      GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
+      GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
     steps:
     - name: Checkout Electron
       uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

+ 6 - 0
.github/workflows/pipeline-electron-build-and-test-and-nan.yml

@@ -49,6 +49,11 @@ on:
         required: true
         type: string
         default: '0'
+      is-asan: 
+        description: 'Building the Address Sanitizer (ASan) Linux build'
+        required: false
+        type: boolean
+        default: false
 
 concurrency:
   group: electron-build-and-test-and-nan-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref }}
@@ -75,6 +80,7 @@ jobs:
       check-runs-on: ${{ inputs.build-runs-on }}
       check-container: ${{ inputs.build-container }}
       gn-build-type: ${{ inputs.gn-build-type }}
+      is-asan: ${{ inputs.is-asan }}
     secrets: inherit
   test:
     uses: ./.github/workflows/pipeline-segment-electron-test.yml

+ 8 - 0
.github/workflows/pipeline-electron-build-and-test.yml

@@ -49,6 +49,11 @@ on:
         required: true
         type: string
         default: '0'
+      is-asan: 
+        description: 'Building the Address Sanitizer (ASan) Linux build'
+        required: false
+        type: boolean
+        default: false
 
 concurrency:
   group: electron-build-and-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref }}
@@ -71,6 +76,7 @@ jobs:
       gn-build-type: ${{ inputs.gn-build-type }}
       generate-symbols: ${{ inputs.generate-symbols }}
       upload-to-storage: ${{ inputs.upload-to-storage }}
+      is-asan: ${{ inputs.is-asan}}
     secrets: inherit
   gn-check:
     uses: ./.github/workflows/pipeline-segment-electron-gn-check.yml
@@ -80,6 +86,7 @@ jobs:
       check-runs-on: ${{ inputs.build-runs-on }}
       check-container: ${{ inputs.build-container }}
       gn-build-type: ${{ inputs.gn-build-type }}
+      is-asan: ${{ inputs.is-asan }}
     secrets: inherit
   test:
     uses: ./.github/workflows/pipeline-segment-electron-test.yml
@@ -89,4 +96,5 @@ jobs:
       target-platform: ${{ inputs.target-platform }}
       test-runs-on: ${{ inputs.test-runs-on }}
       test-container: ${{ inputs.test-container }}
+      is-asan: ${{ inputs.is-asan}}
     secrets: inherit

+ 9 - 1
.github/workflows/pipeline-segment-electron-build.yml

@@ -44,10 +44,15 @@ on:
         required: true
         type: string
         default: '0'
+      is-asan: 
+        description: 'Building the Address Sanitizer (ASan) Linux build'
+        required: false
+        type: boolean
+        default: false
 
 
 concurrency:
-  group: electron-build-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref }}
+  group: electron-build-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.is-asan }}-${{ github.ref }}
   cancel-in-progress: ${{ github.ref != 'refs/heads/main' && !endsWith(github.ref, '-x-y') }}
 
 env:
@@ -102,6 +107,8 @@ jobs:
           fi
         elif [ "${{ inputs.target-arch }}" = "arm64" ]; then
           GN_EXTRA_ARGS='target_cpu="arm64" fatal_linker_warnings=false enable_linux_installer=false'
+        elif [ "${{ inputs.is-asan }}" = true ]; then
+          GN_EXTRA_ARGS='is_asan=true'
         fi
         echo "GN_EXTRA_ARGS=$GN_EXTRA_ARGS" >> $GITHUB_ENV
     - name: Get Depot Tools
@@ -183,6 +190,7 @@ jobs:
         is-release: '${{ inputs.is-release }}'
         generate-symbols: '${{ inputs.generate-symbols }}'
         upload-to-storage: '${{ inputs.upload-to-storage }}'
+        is-asan: '${{ inputs.is-asan }}'
     - name: Set GN_EXTRA_ARGS for MAS Build
       if: ${{ inputs.target-platform == 'macos' }}
       run: |

+ 6 - 1
.github/workflows/pipeline-segment-electron-gn-check.yml

@@ -25,9 +25,14 @@ on:
         required: true
         type: string
         default: testing
+      is-asan: 
+        description: 'Building the Address Sanitizer (ASan) Linux build'
+        required: false
+        type: boolean
+        default: false
 
 concurrency:
-  group: electron-gn-check-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref }}
+  group: electron-gn-check-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.is-asan }}-${{ github.ref }}
   cancel-in-progress: true
 
 env:

+ 37 - 11
.github/workflows/pipeline-segment-electron-test.yml

@@ -20,9 +20,14 @@ on:
         description: 'JSON container information for aks runs-on'
         required: false
         default: '{"image":null}'
+      is-asan: 
+        description: 'Building the Address Sanitizer (ASan) Linux build'
+        required: false
+        type: boolean
+        default: false
 
 concurrency:
-  group: electron-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref }}
+  group: electron-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.is-asan }}-${{ github.ref }}
   cancel-in-progress: ${{ github.ref != 'refs/heads/main' && !endsWith(github.ref, '-x-y') }}
 
 permissions:
@@ -47,6 +52,7 @@ jobs:
     env:
       BUILD_TYPE: ${{ matrix.build-type }}
       TARGET_ARCH: ${{ inputs.target-arch }}
+      ARTIFACT_KEY: ${{ matrix.build-type }}_${{ inputs.target-arch }}
     steps:
     - name: Fix node20 on arm32 runners
       if: ${{ inputs.target-arch == 'arm' }}
@@ -112,16 +118,22 @@ jobs:
         touch .disable_auto_update
     - name: Add Depot Tools to PATH
       run: echo "$(pwd)/depot_tools" >> $GITHUB_PATH
+    - name: Load ASan specific environment variables
+      if: ${{ inputs.is-asan == true }}
+      run: |
+        echo "ARTIFACT_KEY=${{ matrix.build-type }}_${{ inputs.target-arch }}_asan" >> $GITHUB_ENV
+        echo "DISABLE_CRASH_REPORTER_TESTS=true" >> $GITHUB_ENV
+        echo "IS_ASAN=true" >> $GITHUB_ENV
     - name: Download Generated Artifacts
       uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
       with:
-        name: generated_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
+        name: generated_artifacts_${{ env.ARTIFACT_KEY }}
         path: ./generated_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
     - name: Download Src Artifacts
       uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
       with:
-        name: src_artifacts_${{ matrix.build-type }}_${{ env.TARGET_ARCH }}
-        path: ./src_artifacts_${{ matrix.build-type }}_${{ env.TARGET_ARCH }}
+        name: src_artifacts_${{ env.ARTIFACT_KEY }}
+        path: ./src_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
     - name: Restore Generated Artifacts
       run: ./src/electron/script/actions/restore-artifacts.sh
     - name: Unzip Dist, Mksnapshot & Chromedriver
@@ -130,12 +142,12 @@ jobs:
         unzip -:o dist.zip
         unzip -:o chromedriver.zip
         unzip -:o mksnapshot.zip
-    # - name: Import & Trust Self-Signed Codesigning Cert on MacOS
-    #   if: ${{ inputs.target-platform == 'macos' }}
-    #   run: |
-    #     sudo security authorizationdb write com.apple.trust-settings.admin allow
-    #     cd src/electron
-    #     ./script/codesign/generate-identity.sh
+    - name: Import & Trust Self-Signed Codesigning Cert on MacOS
+      if: ${{ inputs.target-platform == 'macos' && inputs.target-arch == 'x64' }}
+      run: |
+        sudo security authorizationdb write com.apple.trust-settings.admin allow
+        cd src/electron
+        ./script/codesign/generate-identity.sh
     - name: Run Electron Tests
       shell: bash
       env:
@@ -159,7 +171,21 @@ jobs:
           chown -R :builduser . && chmod -R g+w .
           chmod 4755 ../out/Default/chrome-sandbox
           runuser -u builduser -- git config --global --add safe.directory $(pwd)
-          runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files
+          if [ "${{ inputs.is-asan }}" == "true" ]; then
+            cd ..
+            ASAN_SYMBOLIZE="$PWD/tools/valgrind/asan/asan_symbolize.py --executable-path=$PWD/out/Default/electron"
+            export ASAN_OPTIONS="symbolize=0 handle_abort=1"
+            export G_SLICE=always-malloc
+            export NSS_DISABLE_ARENA_FREE_LIST=1
+            export NSS_DISABLE_UNLOAD=1
+            export LLVM_SYMBOLIZER_PATH=$PWD/third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer
+            export MOCHA_TIMEOUT=180000
+            echo "Piping output to ASAN_SYMBOLIZE ($ASAN_SYMBOLIZE)"
+            cd electron
+            runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files | $ASAN_SYMBOLIZE
+          else
+            runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files
+          fi
         fi
     - name: Wait for active SSH sessions
       if: always() && !cancelled()

+ 1 - 1
docs/api/system-preferences.md

@@ -2,7 +2,7 @@
 
 > Get system preferences.
 
-Process: [Main](../glossary.md#main-process)
+Process: [Main](../glossary.md#main-process), [Utility](../glossary.md#utility-process)
 
 ```js
 const { systemPreferences } = require('electron')

+ 1 - 1
docs/tutorial/asar-integrity.md

@@ -125,7 +125,7 @@ in the form included below:
 ]
 ```
 
-::: info
+:::info
 
 For an implementation example, see [`src/resedit.ts`](https://github.com/electron/packager/blob/main/src/resedit.ts)
 in the Electron Packager code.

+ 1 - 0
filenames.auto.gni

@@ -350,6 +350,7 @@ auto_filenames = {
 
   utility_bundle_deps = [
     "lib/browser/api/net-fetch.ts",
+    "lib/browser/api/system-preferences.ts",
     "lib/browser/message-port-main.ts",
     "lib/common/api/net-client-request.ts",
     "lib/common/define-properties.ts",

+ 2 - 1
lib/utility/api/module-list.ts

@@ -1,4 +1,5 @@
 // Utility side modules, please sort alphabetically.
 export const utilityNodeModuleList: ElectronInternal.ModuleEntry[] = [
-  { name: 'net', loader: () => require('./net') }
+  { name: 'net', loader: () => require('./net') },
+  { name: 'systemPreferences', loader: () => require('@electron/internal/browser/api/system-preferences') }
 ];

+ 8 - 0
lib/worker/init.ts

@@ -17,6 +17,14 @@ const { makeRequireFunction } = __non_webpack_require__('internal/modules/helper
 global.module = new Module('electron/js2c/worker_init');
 global.require = makeRequireFunction(global.module);
 
+// See WebWorkerObserver::WorkerScriptReadyForEvaluation.
+if ((globalThis as any).blinkfetch) {
+  const keys = ['fetch', 'Response', 'FormData', 'Request', 'Headers'];
+  for (const key of keys) {
+    (globalThis as any)[key] = (globalThis as any)[`blink${key}`];
+  }
+}
+
 // Set the __filename to the path of html file if it is file: protocol.
 // NB. 'self' isn't defined in an AudioWorklet.
 if (typeof self !== 'undefined' && self.location.protocol === 'file:') {

+ 0 - 2
package.json

@@ -19,7 +19,6 @@
     "@types/chai-as-promised": "^7.1.3",
     "@types/dirty-chai": "^2.0.2",
     "@types/express": "^4.17.13",
-    "@types/fs-extra": "^9.0.1",
     "@types/minimist": "^1.2.0",
     "@types/mocha": "^7.0.2",
     "@types/node": "^20.9.0",
@@ -50,7 +49,6 @@
     "events": "^3.2.0",
     "express": "^4.19.2",
     "folder-hash": "^2.1.1",
-    "fs-extra": "^9.0.1",
     "got": "^11.8.5",
     "husky": "^8.0.1",
     "lint": "^1.1.2",

+ 3 - 3
script/gen-filenames.ts

@@ -1,5 +1,5 @@
 import * as cp from 'node:child_process';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as path from 'node:path';
 
@@ -48,7 +48,7 @@ const main = async () => {
   ];
 
   const webpackTargetsWithDeps = await Promise.all(webpackTargets.map(async webpackTarget => {
-    const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-filenames-'));
+    const tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-filenames-'));
     const child = cp.spawn('node', [
       './node_modules/webpack-cli/bin/cli.js',
       '--config', `./build/webpack/${webpackTarget.config}`,
@@ -89,7 +89,7 @@ const main = async () => {
         // Make the generated list easier to read
         .sort()
     };
-    await fs.remove(tmpDir);
+    await fs.promises.rm(tmpDir, { force: true, recursive: true });
     return webpackTargetWithDeps;
   }));
 

+ 5 - 5
script/gn-asar.js

@@ -1,6 +1,6 @@
 const asar = require('@electron/asar');
 const assert = require('node:assert');
-const fs = require('fs-extra');
+const fs = require('node:fs');
 const os = require('node:os');
 const path = require('node:path');
 
@@ -41,12 +41,12 @@ try {
   // Copy all files to a tmp dir to avoid including scrap files in the ASAR
   for (const file of files) {
     const newLocation = path.resolve(tmpPath, path.relative(base[0], file));
-    fs.mkdirsSync(path.dirname(newLocation));
+    fs.mkdirSync(path.dirname(newLocation), { recursive: true });
     fs.writeFileSync(newLocation, fs.readFileSync(file));
   }
 } catch (err) {
   console.error('Unexpected error while generating ASAR', err);
-  fs.remove(tmpPath)
+  fs.promises.rm(tmpPath, { force: true, recursive: true })
     .then(() => process.exit(1))
     .catch(() => process.exit(1));
   return;
@@ -59,5 +59,5 @@ asar.createPackageWithOptions(tmpPath, out[0], {})
       console.error('Unexpected error while generating ASAR', err);
       process.exit(1);
     };
-    fs.remove(tmpPath).then(exit).catch(exit);
-  }).then(() => fs.remove(tmpPath));
+    fs.promises.rm(tmpPath, { force: true, recursive: true }).then(exit).catch(exit);
+  }).then(() => fs.promises.rm(tmpPath, { force: true, recursive: true }));

+ 4 - 2
script/lint.js

@@ -252,8 +252,10 @@ const LINTERS = [{
 
     const allOk = filenames.length > 0 && filenames.map(f => {
       const patchText = fs.readFileSync(f, 'utf8');
-      const subjectAndDescription = /Subject: (.*?)\n\n([\s\S]*?)\s*(?=diff)/ms.exec(patchText);
-      if (!subjectAndDescription[2]) {
+
+      const regex = /Subject: (.*?)\n\n([\s\S]*?)\s*(?=diff)/ms;
+      const subjectAndDescription = regex.exec(patchText);
+      if (!subjectAndDescription?.[2]) {
         console.warn(`Patch file '${f}' has no description. Every patch must contain a justification for why the patch exists and the plan for its removal.`);
         return false;
       }

+ 2 - 2
script/spec-runner.js

@@ -3,7 +3,7 @@
 const { ElectronVersions, Installer } = require('@electron/fiddle-core');
 const childProcess = require('node:child_process');
 const crypto = require('node:crypto');
-const fs = require('fs-extra');
+const fs = require('node:fs');
 const { hashElement } = require('folder-hash');
 const os = require('node:os');
 const path = require('node:path');
@@ -216,7 +216,7 @@ async function installSpecModules (dir) {
     env.npm_config_nodedir = path.resolve(BASE, `out/${utils.getOutDir({ shouldLog: true })}/gen/node_headers`);
   }
   if (fs.existsSync(path.resolve(dir, 'node_modules'))) {
-    await fs.remove(path.resolve(dir, 'node_modules'));
+    await fs.promises.rm(path.resolve(dir, 'node_modules'), { force: true, recursive: true });
   }
   const { status } = childProcess.spawnSync(NPX_CMD, [`yarn@${YARN_VERSION}`, 'install', '--frozen-lockfile'], {
     env,

+ 1 - 2
shell/app/electron_main_delegate.h

@@ -30,10 +30,9 @@ class ElectronMainDelegate : public content::ContentMainDelegate {
   ElectronMainDelegate(const ElectronMainDelegate&) = delete;
   ElectronMainDelegate& operator=(const ElectronMainDelegate&) = delete;
 
-  base::StringPiece GetBrowserV8SnapshotFilename() override;
-
  protected:
   // content::ContentMainDelegate:
+  base::StringPiece GetBrowserV8SnapshotFilename() override;
   std::optional<int> BasicStartupComplete() override;
   void PreSandboxStartup() override;
   void SandboxInitialized(const std::string& process_type) override;

+ 1 - 1
shell/browser/api/electron_api_auto_updater.h

@@ -36,7 +36,7 @@ class AutoUpdater : public gin::Wrappable<AutoUpdater>,
   AutoUpdater();
   ~AutoUpdater() override;
 
-  // Delegate implementations.
+  // auto_updater::Delegate:
   void OnError(const std::string& message) override;
   void OnError(const std::string& message,
                const int code,

+ 1 - 0
shell/browser/api/electron_api_menu_mac.h

@@ -18,6 +18,7 @@ class MenuMac : public Menu {
   explicit MenuMac(gin::Arguments* args);
   ~MenuMac() override;
 
+  // Menu
   void PopupAt(BaseWindow* window,
                int x,
                int y,

+ 1 - 0
shell/browser/api/electron_api_menu_views.h

@@ -21,6 +21,7 @@ class MenuViews : public Menu {
   ~MenuViews() override;
 
  protected:
+  // Menu
   void PopupAt(BaseWindow* window,
                int x,
                int y,

+ 2 - 0
shell/browser/api/electron_api_system_preferences_win.cc

@@ -159,6 +159,8 @@ std::string SystemPreferences::GetMediaAccessStatus(
 }
 
 void SystemPreferences::InitializeWindow() {
+  if (electron::IsUtilityProcess())
+    return;
   // Wait until app is ready before creating sys color listener
   // Creating this listener before the app is ready causes global shortcuts
   // to not fire

+ 1 - 0
shell/browser/api/electron_api_view.cc

@@ -155,6 +155,7 @@ class JSLayoutManager : public views::LayoutManagerBase {
       : layout_callback_(std::move(layout_callback)) {}
   ~JSLayoutManager() override {}
 
+  // views::LayoutManagerBase
   views::ProposedLayout CalculateProposedLayout(
       const views::SizeBounds& size_bounds) const override {
     v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();

+ 3 - 0
shell/browser/api/electron_api_web_contents.h

@@ -170,7 +170,9 @@ class WebContents : public ExclusiveAccessContext,
   void Close(std::optional<gin_helper::Dictionary> options);
   base::WeakPtr<WebContents> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
 
+  // BackgroundThrottlingSource
   bool GetBackgroundThrottling() const override;
+
   void SetBackgroundThrottling(bool allowed);
   int GetProcessID() const;
   base::ProcessId GetOSProcessID() const;
@@ -345,6 +347,7 @@ class WebContents : public ExclusiveAccessContext,
                                           const base::FilePath& file_path);
   v8::Local<v8::Promise> GetProcessMemoryInfo(v8::Isolate* isolate);
 
+  // content::WebContentsDelegate:
   bool HandleContextMenu(content::RenderFrameHost& render_frame_host,
                          const content::ContextMenuParams& params) override;
 

+ 2 - 1
shell/browser/api/frame_subscriber.h

@@ -46,11 +46,12 @@ class FrameSubscriber : private content::WebContentsObserver,
   void AttachToHost(content::RenderWidgetHost* host);
   void DetachFromHost();
 
+  // content::WebContentsObserver
   void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
   void PrimaryPageChanged(content::Page& page) override;
   void RenderViewDeleted(content::RenderViewHost* host) override;
 
-  // viz::mojom::FrameSinkVideoConsumer implementation.
+  // viz::mojom::FrameSinkVideoConsumer
   void OnFrameCaptured(
       ::media::mojom::VideoBufferHandlePtr data,
       ::media::mojom::VideoFrameInfoPtr info,

+ 2 - 0
shell/browser/api/gpu_info_enumerator.h

@@ -30,6 +30,8 @@ class GPUInfoEnumerator final : public gpu::GPUInfo::Enumerator {
  public:
   GPUInfoEnumerator();
   ~GPUInfoEnumerator() override;
+
+  // gpu::GPUInfo::Enumerator
   void AddInt64(const char* name, int64_t value) override;
   void AddInt(const char* name, int value) override;
   void AddString(const char* name, const std::string& value) override;

+ 1 - 0
shell/browser/api/gpuinfo_manager.h

@@ -31,6 +31,7 @@ class GPUInfoManager : private content::GpuDataManagerObserver {
   void FetchBasicInfo(gin_helper::Promise<base::Value> promise);
 
  private:
+  // content::GpuDataManagerObserver
   void OnGpuInfoUpdate() override;
 
   base::Value::Dict EnumerateGPUInfo(gpu::GPUInfo gpu_info) const;

+ 5 - 2
shell/browser/api/message_port.cc

@@ -76,8 +76,11 @@ void MessagePort::PostMessage(gin::Arguments* args) {
     return;
   }
 
-  electron::SerializeV8Value(args->isolate(), message_value,
-                             &transferable_message);
+  if (!electron::SerializeV8Value(args->isolate(), message_value,
+                                  &transferable_message)) {
+    // SerializeV8Value sets an exception.
+    return;
+  }
 
   v8::Local<v8::Value> transferables;
   std::vector<gin::Handle<MessagePort>> wrapped_ports;

+ 2 - 1
shell/browser/browser_process_impl.h

@@ -51,7 +51,6 @@ class BrowserProcessImpl : public BrowserProcess {
 
   static void ApplyProxyModeFromCommandLine(ValueMapPrefStore* pref_store);
 
-  BuildState* GetBuildState() override;
   void PostEarlyInitialization();
   void PreCreateThreads();
   void PreMainMessageLoopRun();
@@ -68,6 +67,8 @@ class BrowserProcessImpl : public BrowserProcess {
   }
 #endif
 
+  // BrowserProcess
+  BuildState* GetBuildState() override;
   void EndSession() override {}
   void FlushLocalStateAndReply(base::OnceClosure reply) override {}
   bool IsShuttingDown() override;

+ 1 - 0
shell/browser/electron_autofill_driver.h

@@ -30,6 +30,7 @@ class AutofillDriver : public mojom::ElectronAutofillDriver {
       mojo::PendingAssociatedReceiver<mojom::ElectronAutofillDriver>
           pending_receiver);
 
+  // mojom::ElectronAutofillDriver
   void ShowAutofillPopup(const gfx::RectF& bounds,
                          const std::vector<std::u16string>& values,
                          const std::vector<std::u16string>& labels) override;

+ 2 - 0
shell/browser/electron_browser_main_parts.cc

@@ -135,6 +135,8 @@ class LinuxUiGetterImpl : public ui::LinuxUiGetter {
  public:
   LinuxUiGetterImpl() = default;
   ~LinuxUiGetterImpl() override = default;
+
+  // ui::LinuxUiGetter
   ui::LinuxUiTheme* GetForWindow(aura::Window* window) override {
     return GetForProfile(nullptr);
   }

+ 1 - 1
shell/browser/electron_navigation_throttle.h

@@ -19,8 +19,8 @@ class ElectronNavigationThrottle : public content::NavigationThrottle {
   ElectronNavigationThrottle& operator=(const ElectronNavigationThrottle&) =
       delete;
 
+  // content::NavigationThrottle
   ElectronNavigationThrottle::ThrottleCheckResult WillStartRequest() override;
-
   ElectronNavigationThrottle::ThrottleCheckResult WillRedirectRequest()
       override;
 

+ 2 - 0
shell/browser/extensions/api/extension_action/extension_action_api.h

@@ -91,6 +91,8 @@ class ExtensionActionFunction : public ExtensionFunction {
  protected:
   ExtensionActionFunction();
   ~ExtensionActionFunction() override;
+
+  // ExtensionFunction
   ResponseAction Run() override;
 
   virtual ResponseAction RunExtensionAction() = 0;

+ 1 - 1
shell/browser/extensions/api/management/electron_management_api_delegate.h

@@ -17,7 +17,7 @@ class ElectronManagementAPIDelegate : public extensions::ManagementAPIDelegate {
   ElectronManagementAPIDelegate();
   ~ElectronManagementAPIDelegate() override;
 
-  // ManagementAPIDelegate.
+  // extensions::ManagementAPIDelegate
   bool LaunchAppFunctionDelegate(
       const extensions::Extension* extension,
       content::BrowserContext* context) const override;

+ 1 - 0
shell/browser/extensions/electron_extensions_browser_api_provider.h

@@ -21,6 +21,7 @@ class ElectronExtensionsBrowserAPIProvider
   ElectronExtensionsBrowserAPIProvider& operator=(
       const ElectronExtensionsBrowserAPIProvider&) = delete;
 
+  // ExtensionsBrowserAPIProvider
   void RegisterExtensionFunctions(ExtensionFunctionRegistry* registry) override;
 };
 

+ 1 - 1
shell/browser/extensions/electron_kiosk_delegate.h

@@ -17,7 +17,7 @@ class ElectronKioskDelegate : public extensions::KioskDelegate {
   ElectronKioskDelegate();
   ~ElectronKioskDelegate() override;
 
-  // KioskDelegate overrides:
+  // extensions::KioskDelegate
   bool IsAutoLaunchedKioskApp(const extensions::ExtensionId& id) const override;
 };
 

+ 1 - 0
shell/browser/native_window_views.cc

@@ -185,6 +185,7 @@ class NativeWindowClientView : public views::ClientView {
   NativeWindowClientView(const NativeWindowClientView&) = delete;
   NativeWindowClientView& operator=(const NativeWindowClientView&) = delete;
 
+  // views::ClientView
   views::CloseRequestResult OnWindowCloseRequested() override {
     window_->NotifyWindowCloseButtonClicked();
     return views::CloseRequestResult::kCannotClose;

+ 1 - 0
shell/browser/net/asar/asar_file_validator.h

@@ -25,6 +25,7 @@ class AsarFileValidator : public mojo::FilteredDataSource::Filter {
   AsarFileValidator(const AsarFileValidator&) = delete;
   AsarFileValidator& operator=(const AsarFileValidator&) = delete;
 
+  // mojo::FilteredDataSource::Filter
   void OnRead(base::span<char> buffer,
               mojo::FileDataSource::ReadResult* result) override;
 

+ 1 - 0
shell/browser/notifications/linux/notification_presenter_linux.h

@@ -16,6 +16,7 @@ class NotificationPresenterLinux : public NotificationPresenter {
   ~NotificationPresenterLinux() override;
 
  private:
+  // NotificationPresenter
   Notification* CreateNotificationObject(
       NotificationDelegate* delegate) override;
 };

+ 1 - 0
shell/browser/notifications/mac/notification_presenter_mac.h

@@ -21,6 +21,7 @@ class NotificationPresenterMac : public NotificationPresenter {
   ~NotificationPresenterMac() override;
 
  private:
+  // NotificationPresenter
   Notification* CreateNotificationObject(
       NotificationDelegate* delegate) override;
 

+ 1 - 0
shell/browser/notifications/platform_notification_service.cc

@@ -53,6 +53,7 @@ class NotificationDelegateImpl final : public electron::NotificationDelegate {
   NotificationDelegateImpl(const NotificationDelegateImpl&) = delete;
   NotificationDelegateImpl& operator=(const NotificationDelegateImpl&) = delete;
 
+  // electron::NotificationDelegate
   void NotificationDestroyed() override { delete this; }
 
   void NotificationClick() override {

+ 1 - 0
shell/browser/notifications/win/notification_presenter_win.h

@@ -41,6 +41,7 @@ class NotificationPresenterWin : public NotificationPresenter {
   std::wstring SaveIconToFilesystem(const SkBitmap& icon, const GURL& origin);
 
  private:
+  // NotificationPresenter
   Notification* CreateNotificationObject(
       NotificationDelegate* delegate) override;
 

+ 5 - 0
shell/browser/notifications/win/windows_toast_notification.h

@@ -120,12 +120,17 @@ class ToastEventHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
   ToastEventHandler(const ToastEventHandler&) = delete;
   ToastEventHandler& operator=(const ToastEventHandler&) = delete;
 
+  // DesktopToastActivatedEventHandler
   IFACEMETHODIMP Invoke(
       ABI::Windows::UI::Notifications::IToastNotification* sender,
       IInspectable* args) override;
+
+  // DesktopToastDismissedEventHandler
   IFACEMETHODIMP Invoke(
       ABI::Windows::UI::Notifications::IToastNotification* sender,
       ABI::Windows::UI::Notifications::IToastDismissedEventArgs* e) override;
+
+  // DesktopToastFailedEventHandler
   IFACEMETHODIMP Invoke(
       ABI::Windows::UI::Notifications::IToastNotification* sender,
       ABI::Windows::UI::Notifications::IToastFailedEventArgs* e) override;

+ 1 - 0
shell/browser/osr/osr_host_display_client.h

@@ -63,6 +63,7 @@ class OffScreenHostDisplayClient : public viz::HostDisplayClient {
   void SetActive(bool active);
 
  private:
+  // viz::HostDisplayClient
 #if BUILDFLAG(IS_MAC)
   void OnDisplayReceivedCALayerParams(
       const gfx::CALayerParams& ca_layer_params) override;

+ 1 - 0
shell/browser/osr/osr_render_widget_host_view.cc

@@ -135,6 +135,7 @@ class ElectronDelegatedFrameHostClient
   ElectronDelegatedFrameHostClient& operator=(
       const ElectronDelegatedFrameHostClient&) = delete;
 
+  // content::DelegatedFrameHostClient
   ui::Layer* DelegatedFrameHostGetLayer() const override {
     return view_->root_layer();
   }

+ 0 - 1
shell/browser/osr/osr_render_widget_host_view.h

@@ -121,7 +121,6 @@ class OffScreenRenderWidgetHostView
 #endif  // BUILDFLAG(IS_MAC)
 
   // content::RenderWidgetHostViewBase:
-
   void UpdateFrameSinkIdRegistration() override;
   void InvalidateLocalSurfaceIdAndAllocationGroup() override;
   void ResetFallbackToFirstNavigationSurface() override;

+ 1 - 0
shell/browser/serial/electron_serial_delegate.h

@@ -29,6 +29,7 @@ class ElectronSerialDelegate : public content::SerialDelegate,
   ElectronSerialDelegate(const ElectronSerialDelegate&) = delete;
   ElectronSerialDelegate& operator=(const ElectronSerialDelegate&) = delete;
 
+  // content::SerialDelegate:
   std::unique_ptr<content::SerialChooser> RunChooser(
       content::RenderFrameHost* frame,
       std::vector<blink::mojom::SerialPortFilterPtr> filters,

+ 1 - 0
shell/browser/ui/file_dialog_linux.cc

@@ -137,6 +137,7 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
     RunOpenDialogImpl(settings);
   }
 
+  // ui::SelectFileDialog::Listener
   void FileSelected(const ui::SelectedFileInfo& file,
                     int index,
                     void* params) override {

+ 1 - 0
shell/browser/ui/inspectable_web_contents.cc

@@ -242,6 +242,7 @@ class InspectableWebContents::NetworkResourceLoader
     response_headers_ = response_head.headers;
   }
 
+  // network::SimpleURLLoaderStreamConsumer
   void OnDataReceived(base::StringPiece chunk,
                       base::OnceClosure resume) override {
     bool encoded = !base::IsStringUTF8(chunk);

+ 1 - 0
shell/browser/ui/message_box_gtk.cc

@@ -182,6 +182,7 @@ class GtkMessageBox : private NativeWindowObserver {
     Show();
   }
 
+  // NativeWindowObserver
   void OnWindowClosed() override {
     parent_->RemoveObserver(this);
     parent_ = nullptr;

+ 1 - 0
shell/browser/ui/tray_icon_cocoa.h

@@ -21,6 +21,7 @@ class TrayIconCocoa : public TrayIcon {
   TrayIconCocoa();
   ~TrayIconCocoa() override;
 
+  // TrayIcon
   void SetImage(const gfx::Image& image) override;
   void SetPressedImage(const gfx::Image& image) override;
   void SetToolTip(const std::string& tool_tip) override;

+ 1 - 0
shell/browser/ui/views/autofill_popup_view.h

@@ -73,6 +73,7 @@ class AutofillPopupView : public views::WidgetDelegateView,
 
   int GetSelectedLine() { return selected_line_.value_or(-1); }
 
+  // views::WidgetDelegateView implementation.
   void WriteDragDataForView(views::View*,
                             const gfx::Point&,
                             ui::OSExchangeData*) override;

+ 1 - 0
shell/browser/ui/views/menu_model_adapter.h

@@ -21,6 +21,7 @@ class MenuModelAdapter : public views::MenuModelAdapter {
   MenuModelAdapter& operator=(const MenuModelAdapter&) = delete;
 
  protected:
+  // views::MenuModelAdapter
   bool GetAccelerator(int id, ui::Accelerator* accelerator) const override;
 
  private:

+ 1 - 1
shell/browser/ui/views/submenu_button.h

@@ -34,9 +34,9 @@ class SubmenuButton : public views::MenuButton {
 
   char16_t accelerator() const { return accelerator_; }
 
+  // views::MenuButton:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
-  // views::MenuButton:
   void PaintButtonContents(gfx::Canvas* canvas) override;
 
  private:

+ 1 - 1
shell/browser/ui/views/win_caption_button.h

@@ -35,7 +35,7 @@ class WinCaptionButton : public views::Button {
   WinCaptionButton(const WinCaptionButton&) = delete;
   WinCaptionButton& operator=(const WinCaptionButton&) = delete;
 
-  // // views::Button:
+  // views::Button:
   gfx::Size CalculatePreferredSize(
       const views::SizeBounds& available_size) const override;
   void OnPaintBackground(gfx::Canvas* canvas) override;

+ 1 - 0
shell/browser/ui/win/electron_desktop_window_tree_host_win.h

@@ -29,6 +29,7 @@ class ElectronDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin,
       const ElectronDesktopWindowTreeHostWin&) = delete;
 
  protected:
+  // views::DesktopWindowTreeHostWin:
   bool PreHandleMSG(UINT message,
                     WPARAM w_param,
                     LPARAM l_param,

+ 1 - 0
shell/browser/webauthn/electron_authenticator_request_delegate.h

@@ -15,6 +15,7 @@ class ElectronWebAuthenticationDelegate
  public:
   ~ElectronWebAuthenticationDelegate() override;
 
+  // content::WebAuthenticationDelegate
   bool SupportsResidentKeys(
       content::RenderFrameHost* render_frame_host) override;
 };

+ 2 - 0
shell/common/gin_helper/function_template.h

@@ -75,6 +75,8 @@ class CallbackHolderBase {
     DisposeObserver(gin::PerIsolateData* per_isolate_data,
                     CallbackHolderBase* holder);
     ~DisposeObserver() override;
+
+    // gin::PerIsolateData::DisposeObserver
     void OnBeforeDispose(v8::Isolate* isolate) override;
     void OnDisposed() override;
 

+ 4 - 3
shell/common/node_bindings.cc

@@ -100,9 +100,10 @@
   V(electron_renderer_ipc)            \
   V(electron_renderer_web_frame)
 
-#define ELECTRON_UTILITY_BINDINGS(V) \
-  V(electron_browser_event_emitter)  \
-  V(electron_common_net)             \
+#define ELECTRON_UTILITY_BINDINGS(V)     \
+  V(electron_browser_event_emitter)      \
+  V(electron_browser_system_preferences) \
+  V(electron_common_net)                 \
   V(electron_utility_parent_port)
 
 #define ELECTRON_TESTING_BINDINGS(V) V(electron_common_testing)

+ 1 - 0
shell/common/node_bindings_linux.h

@@ -15,6 +15,7 @@ class NodeBindingsLinux : public NodeBindings {
   explicit NodeBindingsLinux(BrowserEnvironment browser_env);
 
  private:
+  // NodeBindings
   void PollEvents() override;
 
   // Epoll to poll for uv's backend fd.

+ 1 - 0
shell/common/node_bindings_mac.h

@@ -15,6 +15,7 @@ class NodeBindingsMac : public NodeBindings {
   explicit NodeBindingsMac(BrowserEnvironment browser_env);
 
  private:
+  // NodeBindings
   void PollEvents() override;
 };
 

+ 1 - 0
shell/common/node_bindings_win.h

@@ -15,6 +15,7 @@ class NodeBindingsWin : public NodeBindings {
   explicit NodeBindingsWin(BrowserEnvironment browser_env);
 
  private:
+  // NodeBindings
   void PollEvents() override;
 };
 

+ 1 - 0
shell/common/platform_util_win.cc

@@ -49,6 +49,7 @@ class DeleteFileProgressSink : public IFileOperationProgressSink {
   virtual ~DeleteFileProgressSink() = default;
 
  private:
+  // IFileOperationProgressSink
   ULONG STDMETHODCALLTYPE AddRef(void) override;
   ULONG STDMETHODCALLTYPE Release(void) override;
   HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,

+ 2 - 1
shell/renderer/electron_api_service_impl.h

@@ -32,6 +32,7 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
 
   void BindTo(mojo::PendingReceiver<mojom::ElectronRenderer> receiver);
 
+  // mojom::ElectronRenderer
   void Message(bool internal,
                const std::string& channel,
                blink::CloneableMessage arguments) override;
@@ -50,7 +51,7 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
       mojo::ScopedMessagePipeHandle* interface_pipe) override;
 
  private:
-  // RenderFrameObserver implementation.
+  // content::RenderFrameObserver
   void DidCreateDocumentElement() override;
   void OnDestruct() override;
 

+ 1 - 1
shell/renderer/electron_renderer_pepper_host_factory.h

@@ -25,7 +25,7 @@ class ElectronRendererPepperHostFactory : public ppapi::host::HostFactory {
   ElectronRendererPepperHostFactory& operator=(
       const ElectronRendererPepperHostFactory&) = delete;
 
-  // HostFactory.
+  // ppapi::host::HostFactory
   std::unique_ptr<ppapi::host::ResourceHost> CreateResourceHost(
       ppapi::host::PpapiHost* host,
       PP_Resource resource,

+ 18 - 12
shell/renderer/web_worker_observer.cc

@@ -66,9 +66,25 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
   std::shared_ptr<node::Environment> env =
       node_bindings_->CreateEnvironment(worker_context, nullptr);
 
+  // We need to use the Blink implementation of fetch in web workers
+  // Node.js deletes the global fetch function when their fetch implementation
+  // is disabled, so we need to save and re-add it after the Node.js environment
+  // is loaded. See corresponding change in node/init.ts.
   v8::Local<v8::Object> global = worker_context->Global();
-  v8::Local<v8::String> fetch_string = gin::StringToV8(env->isolate(), "fetch");
-  v8::MaybeLocal<v8::Value> fetch = global->Get(worker_context, fetch_string);
+
+  std::vector<std::string> keys = {"fetch", "Response", "FormData", "Request",
+                                   "Headers"};
+  for (const auto& key : keys) {
+    v8::MaybeLocal<v8::Value> value =
+        global->Get(worker_context, gin::StringToV8(isolate, key.c_str()));
+    if (!value.IsEmpty()) {
+      std::string blink_key = "blink" + key;
+      global
+          ->Set(worker_context, gin::StringToV8(isolate, blink_key.c_str()),
+                value.ToLocalChecked())
+          .Check();
+    }
+  }
 
   // Add Electron extended APIs.
   electron_bindings_->BindTo(env->isolate(), env->process_object());
@@ -76,16 +92,6 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
   // Load everything.
   node_bindings_->LoadEnvironment(env.get());
 
-  // We need to use the Blink implementation of fetch in WebWorker process
-  // Node.js deletes the global fetch function when their fetch implementation
-  // is disabled, so we need to save and re-add it after the Node.js environment
-  // is loaded.
-  if (!fetch.IsEmpty()) {
-    worker_context->Global()
-        ->Set(worker_context, fetch_string, fetch.ToLocalChecked())
-        .Check();
-  }
-
   // Make uv loop being wrapped by window context.
   node_bindings_->set_uv_env(env.get());
 

+ 7 - 1
shell/services/node/parent_port.cc

@@ -44,7 +44,13 @@ void ParentPort::PostMessage(v8::Local<v8::Value> message_value) {
   if (!connector_closed_ && connector_ && connector_->is_valid()) {
     v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
     blink::TransferableMessage transferable_message;
-    electron::SerializeV8Value(isolate, message_value, &transferable_message);
+
+    if (!electron::SerializeV8Value(isolate, message_value,
+                                    &transferable_message)) {
+      // SerializeV8Value sets an exception.
+      return;
+    }
+
     mojo::Message mojo_message =
         blink::mojom::TransferableMessage::WrapAsMessage(
             std::move(transferable_message));

+ 1 - 0
shell/utility/electron_content_utility_client.h

@@ -26,6 +26,7 @@ class ElectronContentUtilityClient : public content::ContentUtilityClient {
   ElectronContentUtilityClient& operator=(const ElectronContentUtilityClient&) =
       delete;
 
+  // content::ContentUtilityClient
   void ExposeInterfacesToBrowser(mojo::BinderMap* binders) override;
   void RegisterMainThreadServices(mojo::ServiceFactory& services) override;
   void RegisterIOThreadServices(mojo::ServiceFactory& services) override;

+ 4 - 4
spec/api-app-spec.ts

@@ -3,7 +3,7 @@ import * as cp from 'node:child_process';
 import * as https from 'node:https';
 import * as http from 'node:http';
 import * as net from 'node:net';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as path from 'node:path';
 import { promisify } from 'node:util';
 import { app, BrowserWindow, Menu, session, net as electronNet, WebContents, utilityProcess } from 'electron/main';
@@ -1118,7 +1118,7 @@ describe('app module', () => {
 
     describe('sessionData', () => {
       const appPath = path.join(__dirname, 'fixtures', 'apps', 'set-path');
-      const appName = fs.readJsonSync(path.join(appPath, 'package.json')).name;
+      const appName = JSON.parse(fs.readFileSync(path.join(appPath, 'package.json'), 'utf8')).name;
       const userDataPath = path.join(app.getPath('appData'), appName);
       const tempBrowserDataPath = path.join(app.getPath('temp'), appName);
 
@@ -1139,8 +1139,8 @@ describe('app module', () => {
       };
 
       beforeEach(() => {
-        fs.removeSync(userDataPath);
-        fs.removeSync(tempBrowserDataPath);
+        fs.rmSync(userDataPath, { force: true, recursive: true });
+        fs.rmSync(tempBrowserDataPath, { force: true, recursive: true });
       });
 
       it('writes to userData by default', () => {

+ 16 - 16
spec/api-autoupdater-darwin-spec.ts

@@ -2,7 +2,7 @@ import { expect } from 'chai';
 import * as cp from 'node:child_process';
 import * as http from 'node:http';
 import * as express from 'express';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as path from 'node:path';
 import * as psList from 'ps-list';
 import { AddressInfo } from 'node:net';
@@ -68,14 +68,14 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
       await withTempDirectory(async (dir) => {
         const secondAppPath = await copyMacOSFixtureApp(dir, fixture);
         const appPJPath = path.resolve(secondAppPath, 'Contents', 'Resources', 'app', 'package.json');
-        await fs.writeFile(
+        await fs.promises.writeFile(
           appPJPath,
-          (await fs.readFile(appPJPath, 'utf8')).replace('1.0.0', version)
+          (await fs.promises.readFile(appPJPath, 'utf8')).replace('1.0.0', version)
         );
         const infoPath = path.resolve(secondAppPath, 'Contents', 'Info.plist');
-        await fs.writeFile(
+        await fs.promises.writeFile(
           infoPath,
-          (await fs.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, `$1${version}`)
+          (await fs.promises.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, `$1${version}`)
         );
         await mutateAppPreSign?.mutate(secondAppPath);
         await signApp(secondAppPath, identity);
@@ -221,9 +221,9 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
         const appPath = await copyMacOSFixtureApp(dir, opts.startFixture);
         await opts.mutateAppPreSign?.mutate(appPath);
         const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
-        await fs.writeFile(
+        await fs.promises.writeFile(
           infoPath,
-          (await fs.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, '$11.0.0')
+          (await fs.promises.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, '$11.0.0')
         );
         await signApp(appPath, identity);
 
@@ -378,9 +378,9 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
             mutationKey: 'prevent-downgrades',
             mutate: async (appPath) => {
               const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
-              await fs.writeFile(
+              await fs.promises.writeFile(
                 infoPath,
-                (await fs.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
+                (await fs.promises.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
               );
             }
           }
@@ -418,9 +418,9 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
             mutationKey: 'prevent-downgrades',
             mutate: async (appPath) => {
               const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
-              await fs.writeFile(
+              await fs.promises.writeFile(
                 infoPath,
-                (await fs.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
+                (await fs.promises.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
               );
             }
           }
@@ -558,7 +558,7 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
 
         await shipItFlipFlopPromise;
         expect(requests).to.have.lengthOf(2, 'should not have relaunched the updated app');
-        expect(JSON.parse(await fs.readFile(path.resolve(appPath, 'Contents/Resources/app/package.json'), 'utf8')).version).to.equal('1.0.0', 'should still be the old version on disk');
+        expect(JSON.parse(await fs.promises.readFile(path.resolve(appPath, 'Contents/Resources/app/package.json'), 'utf8')).version).to.equal('1.0.0', 'should still be the old version on disk');
 
         retainerHandle.kill('SIGINT');
       });
@@ -631,7 +631,7 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
           mutationKey: 'add-resource',
           mutate: async (appPath) => {
             const resourcesPath = path.resolve(appPath, 'Contents', 'Resources', 'app', 'injected.txt');
-            await fs.writeFile(resourcesPath, 'demo');
+            await fs.promises.writeFile(resourcesPath, 'demo');
           }
         }
       }, async (appPath, updateZipPath) => {
@@ -669,8 +669,8 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
           mutationKey: 'modify-shipit',
           mutate: async (appPath) => {
             const shipItPath = path.resolve(appPath, 'Contents', 'Frameworks', 'Squirrel.framework', 'Resources', 'ShipIt');
-            await fs.remove(shipItPath);
-            await fs.symlink('/tmp/ShipIt', shipItPath, 'file');
+            await fs.promises.rm(shipItPath, { force: true, recursive: true });
+            await fs.promises.symlink('/tmp/ShipIt', shipItPath, 'file');
           }
         }
       }, async (appPath, updateZipPath) => {
@@ -708,7 +708,7 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
           mutationKey: 'modify-eframework',
           mutate: async (appPath) => {
             const shipItPath = path.resolve(appPath, 'Contents', 'Frameworks', 'Electron Framework.framework', 'Electron Framework');
-            await fs.appendFile(shipItPath, Buffer.from('123'));
+            await fs.promises.appendFile(shipItPath, Buffer.from('123'));
           }
         }
       }, async (appPath, updateZipPath) => {

+ 4 - 4
spec/api-context-bridge-spec.ts

@@ -1,7 +1,7 @@
 import { BrowserWindow, ipcMain } from 'electron/main';
 import { contextBridge } from 'electron/renderer';
 import { expect } from 'chai';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as http from 'node:http';
 import * as os from 'node:os';
 import * as path from 'node:path';
@@ -34,7 +34,7 @@ describe('contextBridge', () => {
 
   afterEach(async () => {
     await closeWindow(w);
-    if (dir) await fs.remove(dir);
+    if (dir) await fs.promises.rm(dir, { force: true, recursive: true });
   });
 
   it('should not be accessible when contextIsolation is disabled', async () => {
@@ -85,9 +85,9 @@ describe('contextBridge', () => {
         });`}
         (${bindingCreator.toString()})();`;
 
-        const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-'));
+        const tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-'));
         dir = tmpDir;
-        await fs.writeFile(path.resolve(tmpDir, 'preload.js'), worldId === 0 ? preloadContentForMainWorld : preloadContentForIsolatedWorld);
+        await fs.promises.writeFile(path.resolve(tmpDir, 'preload.js'), worldId === 0 ? preloadContentForMainWorld : preloadContentForIsolatedWorld);
         w = new BrowserWindow({
           show: false,
           webPreferences: {

+ 3 - 3
spec/api-safe-storage-spec.ts

@@ -3,7 +3,7 @@ import * as path from 'node:path';
 import { safeStorage } from 'electron/main';
 import { expect } from 'chai';
 import { ifdescribe } from './lib/spec-helpers';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import { once } from 'node:events';
 
 describe('safeStorage module', () => {
@@ -33,8 +33,8 @@ describe('safeStorage module', () => {
 
   after(async () => {
     const pathToEncryptedString = path.resolve(__dirname, 'fixtures', 'api', 'safe-storage', 'encrypted.txt');
-    if (await fs.pathExists(pathToEncryptedString)) {
-      await fs.remove(pathToEncryptedString);
+    if (fs.existsSync(pathToEncryptedString)) {
+      await fs.promises.rm(pathToEncryptedString, { force: true, recursive: true });
     }
   });
 

+ 3 - 3
spec/api-shell-spec.ts

@@ -3,7 +3,7 @@ import { shell } from 'electron/common';
 import { closeAllWindows } from './lib/window-helpers';
 import { ifdescribe, ifit, listen } from './lib/spec-helpers';
 import * as http from 'node:http';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as path from 'node:path';
 import { expect } from 'chai';
@@ -79,9 +79,9 @@ describe('shell module', () => {
     afterEach(closeAllWindows);
 
     it('moves an item to the trash', async () => {
-      const dir = await fs.mkdtemp(path.resolve(app.getPath('temp'), 'electron-shell-spec-'));
+      const dir = await fs.promises.mkdtemp(path.resolve(app.getPath('temp'), 'electron-shell-spec-'));
       const filename = path.join(dir, 'temp-to-be-deleted');
-      await fs.writeFile(filename, 'dummy-contents');
+      await fs.promises.writeFile(filename, 'dummy-contents');
       await shell.trashItem(filename);
       expect(fs.existsSync(filename)).to.be.false();
     });

+ 24 - 0
spec/api-utility-process-spec.ts

@@ -7,6 +7,7 @@ import { closeWindow } from './lib/window-helpers';
 import { once } from 'node:events';
 import { pathToFileURL } from 'node:url';
 import { setImmediate } from 'node:timers/promises';
+import { systemPreferences } from 'electron';
 
 const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'utility-process');
 const isWindowsOnArm = process.platform === 'win32' && process.arch === 'arm64';
@@ -297,6 +298,17 @@ describe('utilityProcess module', () => {
       expect(child.kill()).to.be.true();
       await exit;
     });
+
+    it('handles the parent port trying to send an non-clonable object', async () => {
+      const child = utilityProcess.fork(path.join(fixturesPath, 'non-cloneable.js'));
+      await once(child, 'spawn');
+      child.postMessage('non-cloneable');
+      const [data] = await once(child, 'message');
+      expect(data).to.equal('caught-non-cloneable');
+      const exit = once(child, 'exit');
+      expect(child.kill()).to.be.true();
+      await exit;
+    });
   });
 
   describe('behavior', () => {
@@ -393,6 +405,18 @@ describe('utilityProcess module', () => {
       expect(output).to.include(result);
     });
 
+    ifit(process.platform !== 'linux')('can access exposed main process modules from the utility process', async () => {
+      const message = 'Message from utility process';
+      const child = utilityProcess.fork(path.join(fixturesPath, 'expose-main-process-module.js'));
+      await once(child, 'spawn');
+      child.postMessage(message);
+      const [data] = await once(child, 'message');
+      expect(data).to.equal(systemPreferences.getMediaAccessStatus('screen'));
+      const exit = once(child, 'exit');
+      expect(child.kill()).to.be.true();
+      await exit;
+    });
+
     it('can establish communication channel with sandboxed renderer', async () => {
       const result = 'Message from sandboxed renderer';
       const w = new BrowserWindow({

+ 2 - 2
spec/asar-integrity-spec.ts

@@ -2,7 +2,7 @@ import { expect } from 'chai';
 import * as cp from 'node:child_process';
 import * as nodeCrypto from 'node:crypto';
 import * as originalFs from 'node:original-fs';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as path from 'node:path';
 import { ifdescribe } from './lib/spec-helpers';
@@ -82,7 +82,7 @@ describe('fuses', function () {
   };
 
   beforeEach(async () => {
-    tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-asar-integrity-spec-'));
+    tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-asar-integrity-spec-'));
     appPath = await copyApp(tmpDir);
   });
 

+ 24 - 1
spec/chromium-spec.ts

@@ -1019,12 +1019,35 @@ describe('chromium features', () => {
     });
 
     it('Worker has node integration with nodeIntegrationInWorker', async () => {
-      const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true, contextIsolation: false } });
+      const w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true,
+          nodeIntegrationInWorker: true,
+          contextIsolation: false
+        }
+      });
+
       w.loadURL(`file://${fixturesPath}/pages/worker.html`);
       const [, data] = await once(ipcMain, 'worker-result');
       expect(data).to.equal('object function object function');
     });
 
+    it('Worker has access to fetch-dependent interfaces with nodeIntegrationInWorker', async () => {
+      const w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true,
+          nodeIntegrationInWorker: true,
+          contextIsolation: false
+        }
+      });
+
+      w.loadURL(`file://${fixturesPath}/pages/worker-fetch.html`);
+      const [, data] = await once(ipcMain, 'worker-fetch-result');
+      expect(data).to.equal('function function function function function');
+    });
+
     describe('SharedWorker', () => {
       it('can work', async () => {
         const w = new BrowserWindow({ show: false });

+ 4 - 4
spec/esm-spec.ts

@@ -1,7 +1,7 @@
 import { expect } from 'chai';
 import * as cp from 'node:child_process';
 import { BrowserWindow } from 'electron';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as path from 'node:path';
 import { pathToFileURL } from 'node:url';
@@ -72,15 +72,15 @@ describe('esm', () => {
       if (w) w.close();
       w = null;
       while (tempDirs.length) {
-        await fs.remove(tempDirs.pop()!);
+        await fs.promises.rm(tempDirs.pop()!, { force: true, recursive: true });
       }
     });
 
     async function loadWindowWithPreload (preload: string, webPreferences: Electron.WebPreferences) {
-      const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'e-spec-preload-'));
+      const tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'e-spec-preload-'));
       tempDirs.push(tmpDir);
       const preloadPath = path.resolve(tmpDir, 'preload.mjs');
-      await fs.writeFile(preloadPath, preload);
+      await fs.promises.writeFile(preloadPath, preload);
 
       w = new BrowserWindow({
         show: false,

+ 6 - 0
spec/fixtures/api/utility-process/expose-main-process-module.js

@@ -0,0 +1,6 @@
+const { systemPreferences } = require('electron');
+
+const status = systemPreferences.getMediaAccessStatus('screen');
+process.parentPort.on('message', () => {
+  process.parentPort.postMessage(status);
+});

+ 11 - 0
spec/fixtures/api/utility-process/non-cloneable.js

@@ -0,0 +1,11 @@
+const nonClonableObject = () => {};
+
+process.parentPort.on('message', () => {
+  try {
+    process.parentPort.postMessage(nonClonableObject);
+  } catch (error) {
+    if (/An object could not be cloned/.test(error.message)) {
+      process.parentPort.postMessage('caught-non-cloneable');
+    }
+  }
+});

+ 12 - 0
spec/fixtures/pages/worker-fetch.html

@@ -0,0 +1,12 @@
+<html>
+<body>
+<script type="text/javascript" charset="utf-8">
+  const { ipcRenderer } = require('electron')
+  let worker = new Worker(`../workers/worker_node_fetch.js`)
+  worker.onmessage = function (event) {
+    ipcRenderer.send('worker-fetch-result', event.data)
+    worker.terminate()
+  }
+</script>
+</body>
+</html>

+ 7 - 0
spec/fixtures/workers/worker_node_fetch.js

@@ -0,0 +1,7 @@
+self.postMessage([
+  typeof fetch,
+  typeof Response,
+  typeof Request,
+  typeof Headers,
+  typeof FormData
+].join(' '));

+ 9 - 12
spec/get-files.ts

@@ -1,14 +1,11 @@
-import { once } from 'node:events';
-import * as walkdir from 'walkdir';
+import * as fs from 'node:fs';
+import * as path from 'node:path';
 
-export async function getFiles (directoryPath: string, { filter = null }: {filter?: ((file: string) => boolean) | null} = {}) {
-  const files: string[] = [];
-  const walker = walkdir(directoryPath, {
-    no_recurse: true
-  });
-  walker.on('file', (file) => {
-    if (!filter || filter(file)) { files.push(file); }
-  });
-  await once(walker, 'end');
-  return files;
+export async function getFiles (
+  dir: string,
+  test: ((file: string) => boolean) = (_: string) => true // eslint-disable-line @typescript-eslint/no-unused-vars
+): Promise<string[]> {
+  return fs.promises.readdir(dir)
+    .then(files => files.map(file => path.join(dir, file)))
+    .then(files => files.filter(file => test(file)));
 }

+ 1 - 1
spec/index.js

@@ -148,7 +148,7 @@ app.whenReady().then(async () => {
   };
 
   const { getFiles } = require('./get-files');
-  const testFiles = await getFiles(__dirname, { filter });
+  const testFiles = await getFiles(__dirname, filter);
   for (const file of testFiles.sort()) {
     mocha.addFile(file);
   }

+ 6 - 6
spec/lib/codesign-helpers.ts

@@ -1,5 +1,5 @@
 import * as cp from 'node:child_process';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as path from 'node:path';
 import { expect } from 'chai';
 
@@ -8,7 +8,7 @@ const fixturesPath = path.resolve(__dirname, '..', 'fixtures');
 
 export const shouldRunCodesignTests =
     process.platform === 'darwin' &&
-    !process.env.CI &&
+    !(process.env.CI && process.arch === 'arm64') &&
     !process.mas &&
     !features.isComponentBuild();
 
@@ -37,13 +37,13 @@ export async function copyMacOSFixtureApp (newDir: string, fixture: string | nul
   cp.spawnSync('cp', ['-R', appBundlePath, path.dirname(newPath)]);
   if (fixture) {
     const appDir = path.resolve(newPath, 'Contents/Resources/app');
-    await fs.mkdirp(appDir);
-    await fs.copy(path.resolve(fixturesPath, 'auto-update', fixture), appDir);
+    await fs.promises.mkdir(appDir, { recursive: true });
+    await fs.promises.cp(path.resolve(fixturesPath, 'auto-update', fixture), appDir, { recursive: true });
   }
   const plistPath = path.resolve(newPath, 'Contents', 'Info.plist');
-  await fs.writeFile(
+  await fs.promises.writeFile(
     plistPath,
-    (await fs.readFile(plistPath, 'utf8')).replace('<key>BuildMachineOSBuild</key>', `<key>NSAppTransportSecurity</key>
+    (await fs.promises.readFile(plistPath, 'utf8')).replace('<key>BuildMachineOSBuild</key>', `<key>NSAppTransportSecurity</key>
     <dict>
         <key>NSAllowsArbitraryLoads</key>
         <true/>

+ 2 - 3
spec/lib/fs-helpers.ts

@@ -1,6 +1,5 @@
 import * as cp from 'node:child_process';
 import * as fs from 'original-fs';
-import * as fsExtra from 'fs-extra';
 import * as os from 'node:os';
 import * as path from 'node:path';
 
@@ -20,7 +19,7 @@ export async function copyApp (targetDir: string): Promise<string> {
   const filesToCopy = (fs.readFileSync(zipManifestPath, 'utf-8')).split('\n').filter(f => f !== 'LICENSE' && f !== 'LICENSES.chromium.html' && f !== 'version' && f.trim());
   await Promise.all(
     filesToCopy.map(async rel => {
-      await fsExtra.mkdirp(path.dirname(path.resolve(targetDir, rel)));
+      await fs.promises.mkdir(path.dirname(path.resolve(targetDir, rel)), { recursive: true });
       fs.copyFileSync(path.resolve(baseDir, rel), path.resolve(targetDir, rel));
     })
   );
@@ -29,7 +28,7 @@ export async function copyApp (targetDir: string): Promise<string> {
 }
 
 export async function withTempDirectory (fn: (dir: string) => Promise<void>, autoCleanUp = true) {
-  const dir = await fsExtra.mkdtemp(path.resolve(os.tmpdir(), 'electron-update-spec-'));
+  const dir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-update-spec-'));
   try {
     await fn(dir);
   } finally {

+ 34 - 2
spec/node-spec.ts

@@ -1,6 +1,6 @@
 import { expect } from 'chai';
 import * as childProcess from 'node:child_process';
-import * as fs from 'fs-extra';
+import * as fs from 'node:fs';
 import * as path from 'node:path';
 import * as util from 'node:util';
 import { getRemoteContext, ifdescribe, ifit, itremote, useRemoteContext } from './lib/spec-helpers';
@@ -159,6 +159,38 @@ describe('node feature', () => {
     });
   });
 
+  describe('fetch', () => {
+    itremote('works correctly when nodeIntegration is enabled in the renderer', async (fixtures: string) => {
+      const file = require('node:path').join(fixtures, 'hello.txt');
+      expect(() => {
+        fetch('file://' + file);
+      }).to.not.throw();
+
+      expect(() => {
+        const formData = new FormData();
+        formData.append('username', 'Groucho');
+      }).not.to.throw();
+
+      expect(() => {
+        const request = new Request('https://example.com', {
+          method: 'POST',
+          body: JSON.stringify({ foo: 'bar' })
+        });
+        expect(request.method).to.equal('POST');
+      }).not.to.throw();
+
+      expect(() => {
+        const response = new Response('Hello, world!');
+        expect(response.status).to.equal(200);
+      }).not.to.throw();
+
+      expect(() => {
+        const headers = new Headers();
+        headers.append('Content-Type', 'text/xml');
+      }).not.to.throw();
+    }, [fixtures]);
+  });
+
   it('does not hang when using the fs module in the renderer process', async () => {
     const appPath = path.join(mainFixturesPath, 'apps', 'libuv-hang', 'main.js');
     const appProcess = childProcess.spawn(process.execPath, [appPath], {
@@ -706,7 +738,7 @@ describe('node feature', () => {
           return;
         }
         const alienBinary = path.join(appPath, 'Contents/MacOS/node');
-        await fs.copy(path.join(nodePath, 'node'), alienBinary);
+        await fs.promises.cp(path.join(nodePath, 'node'), alienBinary, { recursive: true });
         // Try to execute electron app from the alien node in app bundle.
         const { code, out } = await spawn(alienBinary, [script, path.join(appPath, 'Contents/MacOS/Electron')]);
         expect(code).to.equal(0);

+ 0 - 1
spec/package.json

@@ -35,7 +35,6 @@
     "split": "^1.0.1",
     "temp": "^0.9.0",
     "uuid": "^3.3.3",
-    "walkdir": "^0.3.2",
     "winreg": "1.2.4",
     "ws": "^7.4.6",
     "yargs": "^16.0.3"

+ 0 - 5
spec/yarn.lock

@@ -2037,11 +2037,6 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
-walkdir@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.3.2.tgz#ac8437a288c295656848ebc19981ebc677a5f590"
-  integrity sha512-0Twghia4Z5wDGDYWURlhZmI47GvERMCsXIu0QZWVVZyW9ZjpbbZvD9Zy9M6cWiQQRRbAcYajIyKNavaZZDt1Uw==
-
 web-streams-polyfill@^3.2.1:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"

+ 0 - 22
yarn.lock

@@ -810,13 +810,6 @@
     "@types/qs" "*"
     "@types/serve-static" "*"
 
-"@types/fs-extra@^9.0.1":
-  version "9.0.1"
-  resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918"
-  integrity sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg==
-  dependencies:
-    "@types/node" "*"
-
 "@types/glob@^7.1.1":
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
@@ -1573,11 +1566,6 @@ asynckit@^0.4.0:
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
 
-at-least-node@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
-  integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
-
 available-typed-arrays@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
@@ -3105,16 +3093,6 @@ fs-extra@^8.1.0:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-extra@^9.0.1:
-  version "9.0.1"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
-  integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==
-  dependencies:
-    at-least-node "^1.0.0"
-    graceful-fs "^4.2.0"
-    jsonfile "^6.0.1"
-    universalify "^1.0.0"
-
 fs-minipass@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"