Search code examples
xcodemacossafari-extensionsafari-web-extension

Safari Web Extension works when archived but not when run from Xcode — ‘Unable to find popup.html’ error”


I'm developing a Safari Web Extension for Mac using Xcode, and I'm encountering a frustrating issue that I can't seem to resolve.

The Problem:

When I archive and export my app and install it, the extension works flawlessly in Safari—no errors, and all resources load correctly. However, when I run the app directly from Xcode (for development and debugging), Safari shows the following error in the extension preferences:

  • "Unable to find 'popup.html' in the extension’s resources."

Sometimes it also shows:

  • "Unable to find 'background.js' in the extension’s resources."

Additionally, the extension doesn't appear in Safari's Extensions list when running from Xcode, even though the app launches.

This used to work and then it just stopped working randomly when I exported a copy of the app by archiving and opened that app to test it. After that I was not able to get it to work by running from Xcode.

What I've Tried So Far:

  • Checked File Inclusion:
    • Verified that popup.html and all other resource files are included in the extension target and are present in the Copy Bundle Resources phase.
    • Confirmed that the files are physically present in the extension bundle under Contents/Resources/.
  • Validated manifest.json**:**
    • Ensured that the paths to popup.html and other resources are correct.
    • Verified that there are no syntax errors.
  • Cleaned Build Artifacts:
    • Performed Clean Build Folder in Xcode.
    • Deleted Derived Data.
    • Restarted Xcode and rebuilt the project.
  • Uninstalled Previous Versions:
    • Removed all instances of the app and extension from /Applications, ~/Applications, and ~/Library/Safari/Extensions/.
    • Cleared Safari caches and preferences related to extensions.
    • Restarted my Mac to ensure all processes are reset.
  • Code Signing and Entitlements:
    • Checked that both the app and extension are properly code-signed with automatic signing enabled.
    • Verified that necessary entitlements are present.
  • Bundle Identifiers and Versions:
    • Confirmed that the bundle identifiers for the app and extension are correct.
    • Incremented the build number.
  • Build Configurations:
    • Tried running the app with both Debug and Release configurations.
    • Adjusted the scheme to ensure the build configuration matches.
  • Tested Moving the App to /Applications**:**
    • Built the app and manually moved it to /Applications.
    • Launched it from there, but the extension still doesn't appear in Safari when running from Xcode.

Additional Observations:

  • The issue seems to occur only when running the app from Xcode. The archived/exported app works without any issues.
  • Safari might be expecting the extension to be in a specific location or have certain properties when the app is run from Xcode.
  • No errors related to popup.html are found when inspecting the built app and extension; the files are present and paths are correct.

Questions:

  • Has anyone experienced similar issues with Safari Web Extensions not loading properly when running the host app from Xcode?
  • Are there specific steps or configurations required to ensure Safari recognizes and loads the extension during development?
  • Could this be related to code signing, entitlements, or macOS security settings when running from Xcode?

Environment:

  • macOS Version: macOS Sequoia 15.2
  • Xcode Version: Xcode 16.1
  • Safari Version: Safari 18.2
  • Development Setup:
    • Safari Web Extension with a macOS host app
    • Using Swift and JavaScript for development

Any insights or suggestions would be greatly appreciated!


Solution

  • Adding this as a workaround until Apple fixes this issue.

    Rather than relying on the Copy Bundle Resources build phase I just manually copied the necessary files by modifying an existing custom shell script which was getting called before Copy Bundle Resources.

    I'll include the entire script here so other people can use it as a starting point and modify it as needed but I can't guarantee that it'll work without modifications.

    Add this shell script into your Resources folder and name it whatever you want.

    Ex: CopyFiles.sh

    #!/bin/bash
    
    set -e # Exit if any command fails
    
    # Function to copy additional resources from source to destination directory since Xcode build phase fails to copy them
    copy_additional_resources() {
        local src_dir="$1"
        local dest_dir="$2"
        
        echo "Copying additional resources from ${src_dir} to ${dest_dir}"
        
        # Copy directories (recursive)
        echo "Copying directories..."
        cp -R "${src_dir}/_locales" "${dest_dir}/"
        cp -R "${src_dir}/images" "${dest_dir}/"
        
        # Copy individual files
        echo "Copying files..."
        cp "${src_dir}/manifest.json" "${dest_dir}/"
        cp "${src_dir}/popup.css" "${dest_dir}/"
        cp "${src_dir}/popup.html" "${dest_dir}/"
    
        # Add additional files/dirs if needed
        
        # List contents to verify
        echo "Contents of destination directory after copying:"
        ls -la "${dest_dir}/"
    }
    
    echo "Environment variable SRCROOT is set to '${SRCROOT}'"
    SRC_RESOURCES="${SRCROOT}/{ENTER_YOUR_EXTENSION_TARGET_DIR_HERE}/Resources"
    echo "SRC_RESOURCES is set to '${SRC_RESOURCES}'"
    
    echo "Environment variable CONTENTS_FOLDER_PATH is set to '${CONTENTS_FOLDER_PATH}'"
    
    # Determine if this is a Mac build by checking if CONTENTS_FOLDER_PATH contains "Contents"
    IS_MAC_BUILD=false
    if [[ "${CONTENTS_FOLDER_PATH}" == *"Contents"* ]]; then
        IS_MAC_BUILD=true
        DEST_DIR="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/Resources"
    else
        DEST_DIR="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}"
    fi
    
    if [ "${CONFIGURATION}" = "DEBUG" ]; then
        echo "Running Debug build: Copying original JS files."
    
        # For debug builds, handle directory creation based on platform
        if [ "$IS_MAC_BUILD" = true ]; then
            echo "Creating Resources directory for Mac build at ${DEST_DIR}."
            mkdir -p "${DEST_DIR}"
        fi
    
        echo "Copying original JS files to destination directory."
        cp "${SRC_RESOURCES}/content.js" "${DEST_DIR}/content.js"
        cp "${SRC_RESOURCES}/background.js" "${DEST_DIR}/background.js"
        cp "${SRC_RESOURCES}/popup.js" "${DEST_DIR}/popup.js"
        
        # List copied files
        echo "Verifying copied JS files in destination directory:"
        ls -la "${DEST_DIR}/"
    
        # Ensure JS files are not executable in debug builds as well
        echo "Removing execute permissions from JS files."
        chmod -x "${DEST_DIR}/content.js"
        chmod -x "${DEST_DIR}/background.js"
        chmod -x "${DEST_DIR}/popup.js"
        
        # Manually additional files and directories
        copy_additional_resources "${SRC_RESOURCES}" "${DEST_DIR}"
    fi
    
    echo "JS files processing completed successfully."
    
    

    Then add a new Build Phase (Run Script phase)

    enter image description here

    Make sure you add the proper input files and output files as seen in the screenshot:

    enter image description here

    Obviously, change the string "Protego Extension" to the name of your extension target's directory

    I left the Copy Bundle Resources build phase at the end of the list since everything was working as expected.

    The primary purpose of my script is to minify my js scripts so I tried to remove the minification logic. Feel free to refactor the script as needed.