diff --git a/.github/wasm-pages/coi-serviceworker.js b/.github/wasm-pages/coi-serviceworker.js new file mode 100644 index 000000000..9901474cc --- /dev/null +++ b/.github/wasm-pages/coi-serviceworker.js @@ -0,0 +1,146 @@ +/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ +let coepCredentialless = false; +if (typeof window === 'undefined') { + self.addEventListener("install", () => self.skipWaiting()); + self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); + + self.addEventListener("message", (ev) => { + if (!ev.data) { + return; + } else if (ev.data.type === "deregister") { + self.registration + .unregister() + .then(() => { + return self.clients.matchAll(); + }) + .then(clients => { + clients.forEach((client) => client.navigate(client.url)); + }); + } else if (ev.data.type === "coepCredentialless") { + coepCredentialless = ev.data.value; + } + }); + + self.addEventListener("fetch", function (event) { + const r = event.request; + if (r.cache === "only-if-cached" && r.mode !== "same-origin") { + return; + } + + const request = (coepCredentialless && r.mode === "no-cors") + ? new Request(r, { + credentials: "omit", + }) + : r; + event.respondWith( + fetch(request) + .then((response) => { + if (response.status === 0) { + return response; + } + + const newHeaders = new Headers(response.headers); + newHeaders.set("Cross-Origin-Embedder-Policy", + coepCredentialless ? "credentialless" : "require-corp" + ); + if (!coepCredentialless) { + newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin"); + } + newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: newHeaders, + }); + }) + .catch((e) => console.error(e)) + ); + }); + +} else { + (() => { + const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf"); + window.sessionStorage.removeItem("coiReloadedBySelf"); + const coepDegrading = (reloadedBySelf == "coepdegrade"); + + // You can customize the behavior of this script through a global `coi` variable. + const coi = { + shouldRegister: () => !reloadedBySelf, + shouldDeregister: () => false, + coepCredentialless: () => true, + coepDegrade: () => true, + doReload: () => window.location.reload(), + quiet: false, + ...window.coi + }; + + const n = navigator; + const controlling = n.serviceWorker && n.serviceWorker.controller; + + // Record the failure if the page is served by serviceWorker. + if (controlling && !window.crossOriginIsolated) { + window.sessionStorage.setItem("coiCoepHasFailed", "true"); + } + const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed"); + + if (controlling) { + // Reload only on the first failure. + const reloadToDegrade = coi.coepDegrade() && !( + coepDegrading || window.crossOriginIsolated + ); + n.serviceWorker.controller.postMessage({ + type: "coepCredentialless", + value: (reloadToDegrade || coepHasFailed && coi.coepDegrade()) + ? false + : coi.coepCredentialless(), + }); + if (reloadToDegrade) { + !coi.quiet && console.log("Reloading page to degrade COEP."); + window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade"); + coi.doReload("coepdegrade"); + } + + if (coi.shouldDeregister()) { + n.serviceWorker.controller.postMessage({ type: "deregister" }); + } + } + + // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are + // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here. + if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return; + + if (!window.isSecureContext) { + !coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required."); + return; + } + + // In some environments (e.g. Firefox private mode) this won't be available + if (!n.serviceWorker) { + !coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode."); + return; + } + + n.serviceWorker.register(window.document.currentScript.src).then( + (registration) => { + !coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope); + + registration.addEventListener("updatefound", () => { + !coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker."); + window.sessionStorage.setItem("coiReloadedBySelf", "updatefound"); + coi.doReload(); + }); + + // If the registration is active, but it's not controlling the page + if (registration.active && !n.serviceWorker.controller) { + !coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker."); + window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling"); + coi.doReload(); + } + }, + (err) => { + !coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err); + } + ); + })(); +} diff --git a/.github/wasm-pages/index.html b/.github/wasm-pages/index.html new file mode 100644 index 000000000..0cb63082d --- /dev/null +++ b/.github/wasm-pages/index.html @@ -0,0 +1,70 @@ + + + + + JANA2 WebAssembly + + + + + +

JANA2 WebAssembly + Dynamic Linking

+

This runs JANA2 natively in the browser with Web Workers (pthreads) and dynamically loads the JTest.so side module.

+ + +

+

+
+    
+    
+    
+
+
diff --git a/.github/workflows/wasm-pages.yml b/.github/workflows/wasm-pages.yml
new file mode 100644
index 000000000..c601ee28d
--- /dev/null
+++ b/.github/workflows/wasm-pages.yml
@@ -0,0 +1,71 @@
+name: Deploy WASM Pages
+
+on:
+  push:
+    branches:
+      - wasm
+  workflow_dispatch:
+
+permissions:
+  contents: read
+  pages: write
+  id-token: write
+
+concurrency:
+  group: "pages"
+  cancel-in-progress: false
+
+jobs:
+  deploy:
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Emscripten
+        uses: mymindstorm/setup-emsdk@v14
+
+      - name: Configure CMake
+        run: |
+          emcmake cmake -B build -S . \
+            -DCMAKE_CXX_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DCMAKE_C_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DCMAKE_EXE_LINKER_FLAGS="-sEXPORTED_RUNTIME_METHODS=callMain,FS -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency -sPROXY_TO_PTHREAD=1" \
+            -DCMAKE_SHARED_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DCMAKE_MODULE_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DBUILD_SHARED_LIBS=ON
+
+      - name: Build jana and JTest
+        run: cmake --build build --target jana JTest -j4
+
+      - name: Prepare Pages Directory
+        run: |
+          mkdir -p dist
+          # Copy our HTML and ServiceWorker
+          cp .github/wasm-pages/index.html dist/
+          cp .github/wasm-pages/coi-serviceworker.js dist/
+          
+          # Copy the WASM artifacts
+          cp build/src/programs/jana/jana.js dist/
+          cp build/src/programs/jana/jana.wasm dist/
+          cp build/src/programs/jana/jana.worker.js dist/ || true
+          
+          cp build/lib/libJANA.so dist/ || cp build/src/libraries/JANA/libJANA.so dist/
+
+          # Copy the plugin side module
+          cp build/lib/JANA/plugins/JTest.so dist/ || cp build/src/plugins/JTest/JTest.so dist/
+
+      - name: Setup Pages
+        uses: actions/configure-pages@v4
+
+      - name: Upload artifact
+        uses: actions/upload-pages-artifact@v3
+        with:
+          path: './dist'
+
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v4
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3aead24a6..3cbc53fcd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,19 @@ cmake_minimum_required(VERSION 3.16)
 cmake_policy(SET CMP0074 NEW) # find_package() uses _ROOT implicit hints
 
 project(jana2 VERSION 2026.03.01)
+include(GNUInstallDirs)
+if(EMSCRIPTEN)
+    set(CMAKE_INSTALL_BINDIR ".")
+    set(CMAKE_INSTALL_LIBDIR ".")
+endif()
+if(EMSCRIPTEN)
+    string(APPEND CMAKE_CXX_FLAGS " -fwasm-exceptions")
+    string(APPEND CMAKE_C_FLAGS " -fwasm-exceptions")
+    set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
+    string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions -sALLOW_MEMORY_GROWTH=1")
+    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions -sALLOW_MEMORY_GROWTH=1")
+    string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions -sALLOW_MEMORY_GROWTH=1")
+endif()
 
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)   # Enable -fPIC for all targets
 
@@ -80,7 +93,7 @@ endif()
 if (${USE_PODIO})
     find_package(podio REQUIRED)
     set(JANA2_HAVE_PODIO 1)
-    set(USE_ROOT ON)
+    # set(USE_ROOT ON) # Disabled for WASM
     include_directories(SYSTEM ${podio_INCLUDE_DIR})
 else()
     set(JANA2_HAVE_PODIO 0)
@@ -170,7 +183,7 @@ if (USE_PERFETTO)
         $<$,$,$>:
             -Wno-sign-compare -Wno-unused-parameter -Wno-missing-field-initializers>
     )
-    install(TARGETS perfetto_sdk EXPORT jana2_targets DESTINATION lib)
+    install(TARGETS perfetto_sdk EXPORT jana2_targets DESTINATION ${CMAKE_INSTALL_LIBDIR})
     install(FILES ${PERFETTO_SDK_DIR}/perfetto.h DESTINATION include/perfetto)
     set(JANA2_HAVE_PERFETTO 1)
 else()
@@ -278,10 +291,10 @@ endif()
 
 #---------------------------------------------------------------------------------------
 
-install(DIRECTORY scripts/ DESTINATION bin FILES_MATCHING PATTERN "jana-*.py"
+install(DIRECTORY scripts/ DESTINATION ${CMAKE_INSTALL_BINDIR} FILES_MATCHING PATTERN "jana-*.py"
         PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE)
 
-    install(FILES "scripts/jana-status.sh" RENAME "jana-status" DESTINATION "bin"
+    install(FILES "scripts/jana-status.sh" RENAME "jana-status" DESTINATION "${CMAKE_INSTALL_BINDIR}"
         PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE)
 
 include(${CMAKE_SOURCE_DIR}/cmake/MakeConfig.cmake)
diff --git a/cmake/AddJanaLibrary.cmake b/cmake/AddJanaLibrary.cmake
index e2667903c..1995289f0 100644
--- a/cmake/AddJanaLibrary.cmake
+++ b/cmake/AddJanaLibrary.cmake
@@ -86,7 +86,7 @@ macro(add_jana_library library_name)
     install(TARGETS ${library_name}
         EXPORT ${LIBRARY_EXPORT}
         PUBLIC_HEADER DESTINATION include/${INSTALL_NAMESPACE}/${CWD_NAME}
-        LIBRARY DESTINATION lib
+        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
     )
 
     # Handle tests
@@ -99,7 +99,7 @@ macro(add_jana_library library_name)
             INSTALL_RPATH_USE_LINK_PATH TRUE
             INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib"
         )
-        #install(TARGETS ${library_name}_tests RUNTIME DESTINATION bin)
+        #install(TARGETS ${library_name}_tests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
         add_test(NAME ${library_name}_tests COMMAND ${library_name}_tests)
         set_tests_properties(${library_name}_tests PROPERTIES
             ENVIRONMENT "LD_LIBRARY_PATH=$:$:$ENV{LD_LIBRARY_PATH}"
diff --git a/cmake/AddJanaPlugin.cmake b/cmake/AddJanaPlugin.cmake
index f10e26448..da73038d3 100644
--- a/cmake/AddJanaPlugin.cmake
+++ b/cmake/AddJanaPlugin.cmake
@@ -60,6 +60,11 @@ macro(add_jana_plugin plugin_name)
     endif()
 
     # Set up target
+    if (EMSCRIPTEN)
+        set(JANA_INSTALL_PLUGINDIR ".")
+    else()
+        set(JANA_INSTALL_PLUGINDIR "${CMAKE_INSTALL_LIBDIR}/${INSTALL_NAMESPACE}/plugins")
+    endif()
     add_library(${plugin_name} SHARED ${PLUGIN_SOURCES})
 
     set_target_properties(${plugin_name} PROPERTIES
@@ -91,7 +96,7 @@ macro(add_jana_plugin plugin_name)
     install(TARGETS ${plugin_name}
         EXPORT ${PLUGIN_EXPORT}
         PUBLIC_HEADER DESTINATION include/${INSTALL_NAMESPACE}/plugins/${plugin_name}
-        LIBRARY DESTINATION lib/${INSTALL_NAMESPACE}/plugins
+        LIBRARY DESTINATION ${JANA_INSTALL_PLUGINDIR}
     )
 
     # Handle tests
@@ -104,7 +109,7 @@ macro(add_jana_plugin plugin_name)
             INSTALL_RPATH_USE_LINK_PATH TRUE
             INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/lib/${INSTALL_NAMESPACE}/plugins"
         )
-        #install(TARGETS ${plugin_name}-tests RUNTIME DESTINATION bin)
+        #install(TARGETS ${plugin_name}-tests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
         add_test(NAME ${plugin_name}-tests COMMAND ${plugin_name}-tests)
         set_tests_properties(${plugin_name}-tests PROPERTIES
             ENVIRONMENT "JANA_PLUGIN_PATH=${CMAKE_BINARY_DIR}/lib/JANA/plugins;LD_LIBRARY_PATH=$:$ENV{LD_LIBRARY_PATH}"
diff --git a/cmake/AddJanaTest.cmake b/cmake/AddJanaTest.cmake
index c62b646a8..0f83a6df5 100644
--- a/cmake/AddJanaTest.cmake
+++ b/cmake/AddJanaTest.cmake
@@ -33,7 +33,7 @@ macro(add_jana_test test_target_name)
         INSTALL_RPATH_USE_LINK_PATH TRUE
         INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/lib/JANA/plugins")
 
-    install(TARGETS ${test_target_name} RUNTIME DESTINATION bin)
+    install(TARGETS ${test_target_name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
     add_test(NAME ${test_target_name} COMMAND ${test_target_name})
     set_tests_properties(${test_target_name} PROPERTIES
diff --git a/cmake/MakeJanaThis.cmake b/cmake/MakeJanaThis.cmake
index 9335c34a9..809602508 100644
--- a/cmake/MakeJanaThis.cmake
+++ b/cmake/MakeJanaThis.cmake
@@ -148,6 +148,6 @@ configure_file(scripts/jana-config.in jana-config @ONLY)
 configure_file(scripts/jana-this.sh.in  jana-this.sh  @ONLY)
 configure_file(scripts/jana-this.csh.in jana-this.csh  @ONLY)
 
-install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-config DESTINATION bin)
-install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.sh  DESTINATION bin)
-install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.csh  DESTINATION bin)
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-config DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.sh  DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.csh  DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/examples/misc/PodioDatamodel/CMakeLists.txt b/src/examples/misc/PodioDatamodel/CMakeLists.txt
index eb6bab7cf..1674e29b5 100644
--- a/src/examples/misc/PodioDatamodel/CMakeLists.txt
+++ b/src/examples/misc/PodioDatamodel/CMakeLists.txt
@@ -14,17 +14,17 @@ if (USE_PODIO)
 
     install(TARGETS PodioDatamodel
         EXPORT jana2_targets
-        LIBRARY DESTINATION lib
+        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
         PUBLIC_HEADER DESTINATION include/JANA/examples/PodioDatamodel
     )
 
     install(TARGETS PodioDatamodelDict
         EXPORT jana2_targets
-        DESTINATION lib
+        DESTINATION ${CMAKE_INSTALL_LIBDIR}
     )
 
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PodioDatamodelDictDict.rootmap DESTINATION lib)
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libPodioDatamodelDict_rdict.pcm DESTINATION lib)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PodioDatamodelDictDict.rootmap DESTINATION ${CMAKE_INSTALL_LIBDIR})
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libPodioDatamodelDict_rdict.pcm DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
 else()
     message(STATUS "Skipping examples/PodioDatamodel because USE_PODIO=Off")
diff --git a/src/examples/misc/RootDatamodelExample/CMakeLists.txt b/src/examples/misc/RootDatamodelExample/CMakeLists.txt
index b69436391..862ee5466 100644
--- a/src/examples/misc/RootDatamodelExample/CMakeLists.txt
+++ b/src/examples/misc/RootDatamodelExample/CMakeLists.txt
@@ -41,7 +41,7 @@ if(${USE_ROOT})
     target_link_libraries(RootDatamodelExample PUBLIC ROOT::Core)
 
     message(STATUS "Installing ROOT PCM files: ${my_pcms}")
-    install(FILES ${my_pcms} DESTINATION lib/JANA/plugins)
+    install(FILES ${my_pcms} DESTINATION ${JANA_INSTALL_PLUGINDIR})
 
     add_test(NAME jana-example-rootdatamodel-tests
         COMMAND $ -Pplugins=RootDatamodelExample -Pjana:nevents=10)
diff --git a/src/examples/misc/SubeventCUDAExample/CMakeLists.txt b/src/examples/misc/SubeventCUDAExample/CMakeLists.txt
index c3ee3f7e4..09c93689e 100644
--- a/src/examples/misc/SubeventCUDAExample/CMakeLists.txt
+++ b/src/examples/misc/SubeventCUDAExample/CMakeLists.txt
@@ -16,7 +16,7 @@ if (USE_CUDA)
         OUTPUT_NAME "SubeventCUDAExample"
 		CUDA_ARCHITECTURES "75;80")
 
-	install(TARGETS SubeventCUDAExample DESTINATION bin)
+	install(TARGETS SubeventCUDAExample DESTINATION ${CMAKE_INSTALL_BINDIR})
     add_test(NAME jana-example-subevent-cuda-tests
         COMMAND SubeventCUDAExample)
 
diff --git a/src/examples/misc/SubeventExample/CMakeLists.txt b/src/examples/misc/SubeventExample/CMakeLists.txt
index 351a260f9..a1d45e54c 100644
--- a/src/examples/misc/SubeventExample/CMakeLists.txt
+++ b/src/examples/misc/SubeventExample/CMakeLists.txt
@@ -3,7 +3,7 @@
 add_executable(SubeventExample SubeventExample.cc)
 target_link_libraries(SubeventExample jana2)
 set_target_properties(SubeventExample PROPERTIES PREFIX "" OUTPUT_NAME "SubeventExample")
-install(TARGETS SubeventExample DESTINATION bin)
+install(TARGETS SubeventExample DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 add_test(NAME jana-example-subevent-tests
     COMMAND SubeventExample)
diff --git a/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt b/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt
index ac74de407..b89c48fdf 100644
--- a/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt
+++ b/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt
@@ -9,5 +9,5 @@ target_link_libraries(lw-tutorial-recon PRIVATE
 )
 # Note that if you are outside the JANA2 source tree, you need to target_link_libraries against JANA::jana2_shared_lib instead
 
-install(TARGETS lw-tutorial-recon DESTINATION bin)
+install(TARGETS lw-tutorial-recon DESTINATION ${CMAKE_INSTALL_BINDIR})
 
diff --git a/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt b/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt
index 75a652376..5142c6c46 100644
--- a/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt
+++ b/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt
@@ -13,15 +13,15 @@ PODIO_ADD_ROOT_IO_DICT(jana2_tutorial_podio_datamodel_dict jana2_tutorial_podio_
 
 install(TARGETS jana2_tutorial_podio_datamodel
     EXPORT jana2_targets
-    LIBRARY DESTINATION lib
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
     PUBLIC_HEADER DESTINATION include/jana2_tutorial_podio_datamodel
 )
 
 install(TARGETS jana2_tutorial_podio_datamodel_dict
     EXPORT jana2_targets
-    DESTINATION lib
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
 )
 
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jana2_tutorial_podio_datamodel_dictDict.rootmap DESTINATION lib)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjana2_tutorial_podio_datamodel_dict_rdict.pcm DESTINATION lib)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jana2_tutorial_podio_datamodel_dictDict.rootmap DESTINATION ${CMAKE_INSTALL_LIBDIR})
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjana2_tutorial_podio_datamodel_dict_rdict.pcm DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
diff --git a/src/libraries/JANA/CLI/JBenchmarker.cc b/src/libraries/JANA/CLI/JBenchmarker.cc
index 46e59a7ce..0e8b92751 100644
--- a/src/libraries/JANA/CLI/JBenchmarker.cc
+++ b/src/libraries/JANA/CLI/JBenchmarker.cc
@@ -1,3 +1,6 @@
+#ifdef __EMSCRIPTEN__
+#include 
+#endif
 
 // Copyright 2020, Jefferson Science Associates, LLC.
 // Subject to the terms in the LICENSE file found in the top-level directory.
@@ -192,6 +195,87 @@ void JBenchmarker::RunUntilFinished() {
     // Hopefully, because we called flush(), the files will be partially filled even if we are SIGKILLed
     // before we reach this point.
 
+#ifdef __EMSCRIPTEN__
+
+    MAIN_THREAD_EM_ASM({
+
+        var rates_path = UTF8ToString($0);
+
+        var samples_path = UTF8ToString($1);
+
+        if (typeof window !== "undefined" && typeof Blob !== "undefined") {
+
+            function triggerDownload(path) {
+
+                try {
+
+                    var content = FS.readFile(path);
+
+                    var blob = new Blob([content], { type: "application/octet-stream" });
+
+                    var url = URL.createObjectURL(blob);
+
+                    var a = document.createElement("a");
+
+                    a.href = url;
+
+                    var parts = path.split("/");
+
+                    a.download = parts[parts.length - 1];
+
+                    document.body.appendChild(a);
+
+                    a.click();
+
+                    document.body.removeChild(a);
+
+                    URL.revokeObjectURL(url);
+
+                } catch(e) {
+
+                    console.error("Failed to download " + path, e);
+
+                }
+
+            }
+
+            triggerDownload(rates_path);
+
+            setTimeout(function() { triggerDownload(samples_path); }, 500);
+
+        } else if (typeof ENVIRONMENT_IS_NODE !== "undefined" && ENVIRONMENT_IS_NODE) {
+
+            var fs = require("fs");
+
+            function copyOut(path) {
+
+                try {
+
+                    var content = FS.readFile(path);
+
+                    var parts = path.split("/");
+
+                    fs.writeFileSync(parts[parts.length - 1], content);
+
+                } catch(e) {
+
+                    console.error("Failed to extract " + path, e);
+
+                }
+
+            }
+
+            copyOut(rates_path);
+
+            copyOut(samples_path);
+
+        }
+
+    }, (m_output_dir + "/" + m_rates_filename).c_str(), (m_output_dir + "/" + m_samples_filename).c_str());
+
+#endif
+
+
     samples_file.close();
     rates_file.close();
 
diff --git a/src/libraries/JANA/CMakeLists.txt b/src/libraries/JANA/CMakeLists.txt
index e2e5a9fec..6a2824c33 100644
--- a/src/libraries/JANA/CMakeLists.txt
+++ b/src/libraries/JANA/CMakeLists.txt
@@ -83,7 +83,10 @@ if (USE_PERFETTO)
 endif()
 
 if (${USE_PODIO})
-    target_link_libraries(jana2 PUBLIC podio::podio podio::podioRootIO)
+    target_link_libraries(jana2 PUBLIC podio::podio)
+    if(USE_ROOT)
+        target_link_libraries(jana2 PUBLIC podio::podioRootIO)
+    endif()
 elseif (${USE_ROOT})
     target_link_libraries(jana2 PUBLIC ROOT::Core)
 endif()
@@ -102,11 +105,14 @@ if (USE_PERFETTO)
 endif()
 
 if (${USE_PODIO})
-    target_link_libraries(jana2_static_lib PUBLIC podio::podio podio::podioRootIO)
+    target_link_libraries(jana2_static_lib PUBLIC podio::podio)
+    if(USE_ROOT)
+        target_link_libraries(jana2_static_lib PUBLIC podio::podioRootIO)
+    endif()
 elseif (${USE_ROOT})
     target_link_libraries(jana2_static_lib PUBLIC ROOT::Core)
 endif()
-install(TARGETS jana2_static_lib EXPORT jana2_targets DESTINATION lib)
+install(TARGETS jana2_static_lib EXPORT jana2_targets DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
 
 # Build shared library
@@ -122,12 +128,15 @@ if (USE_PERFETTO)
 endif()
 
 if (${USE_PODIO})
-    target_link_libraries(jana2_shared_lib PUBLIC podio::podio podio::podioRootIO)
+    target_link_libraries(jana2_shared_lib PUBLIC podio::podio)
+    if(USE_ROOT)
+        target_link_libraries(jana2_shared_lib PUBLIC podio::podioRootIO)
+    endif()
 elseif (${USE_ROOT})
     target_link_libraries(jana2_shared_lib PUBLIC ROOT::Core)
 endif()
 
-install(TARGETS jana2_shared_lib EXPORT jana2_targets DESTINATION lib)
+install(TARGETS jana2_shared_lib EXPORT jana2_targets DESTINATION ${CMAKE_INSTALL_LIBDIR})
 set(INSTALL_RPATH_USE_LINK_PATH True)
 
 
diff --git a/src/libraries/JANA/Engine/JExecutionEngine.cc b/src/libraries/JANA/Engine/JExecutionEngine.cc
index 60fd6a94e..382378136 100644
--- a/src/libraries/JANA/Engine/JExecutionEngine.cc
+++ b/src/libraries/JANA/Engine/JExecutionEngine.cc
@@ -324,7 +324,9 @@ void JExecutionEngine::HandleFailures() {
         if (worker->is_timed_out) {
             std::string arrow_name = (worker->last_arrow_id == static_cast(-1)) ? "(none)" : m_topology->GetArrows()[worker->last_arrow_id]->GetName();
             LOG_FATAL(GetLogger()) << "Timeout in worker thread " << worker->worker_id << " while executing " << arrow_name << " on event #" << worker->last_event_nr << LOG_END;
+#ifndef __EMSCRIPTEN__
             pthread_kill(worker->thread->native_handle(), SIGUSR2);
+#endif
             LOG_INFO(GetLogger()) << "Worker thread signalled; waiting for backtrace capture." << LOG_END;
             worker->backtrace.WaitForCapture();
         }
@@ -821,7 +823,9 @@ void JExecutionEngine::PrintWorkerReport(bool send_to_pipe) {
     LOG_INFO(GetLogger()) << "Generating worker report. It may take some time to retrieve each symbol's debug information." << LOG_END;
     for (auto& worker: m_worker_states) {
         worker->backtrace.Reset();
+#ifndef __EMSCRIPTEN__
         pthread_kill(worker->thread->native_handle(), SIGUSR2);
+#endif
     }
     for (auto& worker: m_worker_states) {
         worker->backtrace.WaitForCapture();
diff --git a/src/libraries/JANA/Services/JPluginLoader.cc b/src/libraries/JANA/Services/JPluginLoader.cc
index 568ec9839..7bbad871a 100644
--- a/src/libraries/JANA/Services/JPluginLoader.cc
+++ b/src/libraries/JANA/Services/JPluginLoader.cc
@@ -138,6 +138,10 @@ void JPluginLoader::attach_plugins(JComponentManager* jcm) {
         // User didn't provide a path, so we have to search
         // If no valid paths found, `path` variable stays empty
 
+#ifdef __EMSCRIPTEN__
+        if (path.empty()) path = name + ".so";
+#endif
+
         if (path.empty()) {
             LOG_ERROR(m_logger) 
                 << "Couldn't find plugin '" << name << "'\n" 
diff --git a/src/libraries/JANA/Utils/JBacktrace.cc b/src/libraries/JANA/Utils/JBacktrace.cc
index 6ccbdb6e4..94550488e 100644
--- a/src/libraries/JANA/Utils/JBacktrace.cc
+++ b/src/libraries/JANA/Utils/JBacktrace.cc
@@ -5,6 +5,60 @@
 
 #include "JBacktrace.h"
 
+#ifdef __EMSCRIPTEN__
+#include 
+
+JBacktrace::JBacktrace(const JBacktrace& other) {
+    m_ready = other.m_ready.load();
+    m_frame_count = other.m_frame_count;
+    m_frames_to_omit = other.m_frames_to_omit;
+    m_buffer = other.m_buffer;
+}
+
+JBacktrace& JBacktrace::operator=(const JBacktrace& other) {
+    m_ready = other.m_ready.load();
+    m_frame_count = other.m_frame_count;
+    m_frames_to_omit = other.m_frames_to_omit;
+    m_buffer = other.m_buffer;
+    return *this;
+}
+
+JBacktrace::JBacktrace(JBacktrace&& other) {
+    m_ready = other.m_ready.load();
+    m_frame_count = other.m_frame_count;
+    m_frames_to_omit = other.m_frames_to_omit;
+    m_buffer = std::move(other.m_buffer);
+}
+
+void JBacktrace::Reset() {
+    m_ready = false;
+}
+
+void JBacktrace::WaitForCapture() const {
+}
+
+void JBacktrace::Capture(int frames_to_omit) {
+    m_frame_count = 0;
+    m_frames_to_omit = frames_to_omit;
+    m_ready.store(true, std::memory_order_release);
+}
+
+void JBacktrace::Format(std::ostream& os) const {
+    os << "Backtrace not supported on Emscripten." << std::endl;
+}
+
+std::string JBacktrace::AddrToLineInfo(const char* filename, size_t offset) const {
+    return "";
+}
+
+std::string JBacktrace::ToString() const {
+    std::ostringstream oss;
+    Format(oss);
+    return oss.str();
+}
+
+#else
+
 #include 
 #include 
 #include 
@@ -130,5 +184,6 @@ std::string JBacktrace::ToString() const {
     return oss.str();
 }
 
+#endif
 
 
diff --git a/src/libraries/JANA/Utils/JCpuInfo.cc b/src/libraries/JANA/Utils/JCpuInfo.cc
index 6bd72b432..b6d5248b3 100644
--- a/src/libraries/JANA/Utils/JCpuInfo.cc
+++ b/src/libraries/JANA/Utils/JCpuInfo.cc
@@ -68,6 +68,8 @@ uint32_t GetCpuID() {
 #endif // __cpuid_count
     return cpuid;
 
+#elif defined(__EMSCRIPTEN__)
+    return 0;
 #else //__APPLE__
     return sched_getcpu();
 #endif //__APPLE__
@@ -75,7 +77,9 @@ uint32_t GetCpuID() {
 
 
 bool PinThreadToCpu(std::thread* thread, size_t cpu_id) {
-
+#ifdef __EMSCRIPTEN__
+    return false;
+#else
     if (typeid(std::thread::native_handle_type) != typeid(pthread_t)) {
         return false;
     }
@@ -99,6 +103,7 @@ bool PinThreadToCpu(std::thread* thread, size_t cpu_id) {
     }
 #endif
     return true;
+#endif // __EMSCRIPTEN__
 }
 
 } // JCpuInfo namespace
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index f3db8d855..505e1f940 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -1,10 +1,12 @@
-
 add_subdirectory(JTest)
 add_subdirectory(janacontrol)
 add_subdirectory(janadot)
 add_subdirectory(janarate)
-add_subdirectory(janaroot)
-add_subdirectory(janaview)
+if(USE_ROOT)
+    add_subdirectory(janaroot)
+    if(TARGET ROOT::Gui)
+        add_subdirectory(janaview)
+    endif()
+endif()
 add_subdirectory(regressiontest)
 add_subdirectory(perfetto)
-
diff --git a/src/plugins/janaview/CMakeLists.txt b/src/plugins/janaview/CMakeLists.txt
index 9bc7ae051..21d56d2ab 100644
--- a/src/plugins/janaview/CMakeLists.txt
+++ b/src/plugins/janaview/CMakeLists.txt
@@ -12,8 +12,8 @@ if(USE_ROOT)
         PUBLIC_HEADER ${JANAVIEW_HEADERS}
     )
 
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe_rdict.pcm DESTINATION lib/JANA/plugins)
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe.rootmap DESTINATION lib/JANA/plugins)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe_rdict.pcm DESTINATION ${JANA_INSTALL_PLUGINDIR})
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe.rootmap DESTINATION ${JANA_INSTALL_PLUGINDIR})
 
 else()
 
diff --git a/src/programs/jana/CMakeLists.txt b/src/programs/jana/CMakeLists.txt
index 9ed2f11ff..f7a80b36c 100644
--- a/src/programs/jana/CMakeLists.txt
+++ b/src/programs/jana/CMakeLists.txt
@@ -5,5 +5,12 @@ add_executable(jana jana.cc )
 find_package(Threads REQUIRED)
 target_link_libraries(jana jana2_shared_lib)
 target_link_options(jana PRIVATE -rdynamic)
-install(TARGETS jana DESTINATION bin)
+install(TARGETS jana DESTINATION ${CMAKE_INSTALL_BINDIR})
 
+
+
+if(EMSCRIPTEN)
+    target_link_options(jana PRIVATE "-sEXPORTED_RUNTIME_METHODS=callMain,FS")
+    install(FILES $/jana.wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
+    install(FILES $/jana.worker.js DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
+endif()
diff --git a/src/programs/perf_tests/BasicTopology.cc b/src/programs/perf_tests/BasicTopology.cc
index c9e21a9aa..a7492a474 100644
--- a/src/programs/perf_tests/BasicTopology.cc
+++ b/src/programs/perf_tests/BasicTopology.cc
@@ -29,7 +29,7 @@ struct PESrc : public JEventSource {
         data_out.SetShortName("1");
     }
     JEventSource::Result Emit(JEvent& event) override {
-        data_out().push_back(new Data {event.GetEventNumber()*3 });
+        data_out().push_back(new Data {static_cast(event.GetEventNumber()*3) });
         JBenchUtils::consume_cpu_us(*latency_us);
         return Result::Success;
     };
diff --git a/src/programs/perf_tests/SourceTopology.cc b/src/programs/perf_tests/SourceTopology.cc
index 63d603d49..1446de864 100644
--- a/src/programs/perf_tests/SourceTopology.cc
+++ b/src/programs/perf_tests/SourceTopology.cc
@@ -25,7 +25,7 @@ struct Src : public JEventSource {
         data_out.SetShortName("1");
     }
     JEventSource::Result Emit(JEvent& block) override {
-        data_out().push_back(new Data {block.GetEventNumber()*3 });
+        data_out().push_back(new Data {static_cast(block.GetEventNumber()*3) });
         JBenchUtils::consume_cpu_us(*latency_us);
         return Result::Success;
     };
diff --git a/src/programs/perf_tests/TapChainTopology.cc b/src/programs/perf_tests/TapChainTopology.cc
index b38dc9363..ce879093f 100644
--- a/src/programs/perf_tests/TapChainTopology.cc
+++ b/src/programs/perf_tests/TapChainTopology.cc
@@ -25,7 +25,7 @@ struct Src : public JEventSource {
         data_out.SetShortName("1");
     }
     JEventSource::Result Emit(JEvent& evt) override {
-        data_out().push_back(new Data {evt.GetEventNumber()*3 });
+        data_out().push_back(new Data {static_cast(evt.GetEventNumber()*3) });
         JBenchUtils::consume_cpu_us(*latency_us);
         return Result::Success;
     };
diff --git a/src/programs/perf_tests/UnfoldTopology.cc b/src/programs/perf_tests/UnfoldTopology.cc
index 918fc41e9..0cb886f97 100644
--- a/src/programs/perf_tests/UnfoldTopology.cc
+++ b/src/programs/perf_tests/UnfoldTopology.cc
@@ -27,7 +27,7 @@ struct BSrc : public JEventSource {
         data_out.SetShortName("1");
     }
     JEventSource::Result Emit(JEvent& block) override {
-        data_out().push_back(new Data {block.GetEventNumber()*3 });
+        data_out().push_back(new Data {static_cast(block.GetEventNumber()*3) });
         JBenchUtils::consume_cpu_us(*latency_us);
         return Result::Success;
     };