diff --git a/app/assets/javascripts/discourse/app/app.js b/app/assets/javascripts/discourse/app/app.js
index be305596c42..19da3d28362 100644
--- a/app/assets/javascripts/discourse/app/app.js
+++ b/app/assets/javascripts/discourse/app/app.js
@@ -81,15 +81,6 @@ const Discourse = Application.extend({
initialize: () => withPluginApi(cb.version, cb.code),
});
});
-
- window.addEventListener(
- "load",
- () => {
- // The app booted. Remove the splash screen
- document.querySelector("#d-splash")?.remove();
- },
- { once: true }
- );
},
_registerPluginCode(version, code) {
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de081f8b943..eeee4f410e0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -135,6 +135,10 @@ module ApplicationHelper
path
end
+ def self.splash_screen_nonce
+ @splash_screen_nonce ||= SecureRandom.hex
+ end
+
def preload_script(script)
scripts = [script]
diff --git a/app/views/common/_discourse_splash.html.erb b/app/views/common/_discourse_splash.html.erb
index 40b1fb1f009..3b36873b18b 100644
--- a/app/views/common/_discourse_splash.html.erb
+++ b/app/views/common/_discourse_splash.html.erb
@@ -9,13 +9,14 @@
display: grid;
place-items: center;
backface-visibility: hidden;
+ background: var(--secondary);
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
- z-index: 1001; /* above the header */
- background: var(--secondary);
+ z-index: 1001;
+ --animation-state: paused;
}
#d-splash .preloader-image {
@@ -28,8 +29,9 @@
position: absolute;
opacity: 0;
animation: fade-in 0.5s ease-in-out;
- animation-delay: 1.25s;
+ animation-delay: 1s;
animation-fill-mode: forwards;
+ animation-play-state: var(--animation-state);
color: var(--primary);
}
@@ -74,7 +76,7 @@
@@ -93,5 +95,59 @@
}
+
+
<%- end %>
diff --git a/lib/content_security_policy/default.rb b/lib/content_security_policy/default.rb
index 1350874000e..515dde89017 100644
--- a/lib/content_security_policy/default.rb
+++ b/lib/content_security_policy/default.rb
@@ -73,6 +73,10 @@ class ContentSecurityPolicy
sources << 'https://www.googletagmanager.com/gtm.js'
sources << "'nonce-#{ApplicationHelper.google_tag_manager_nonce}'"
end
+
+ if SiteSetting.splash_screen
+ sources << "'nonce-#{ApplicationHelper.splash_screen_nonce}'"
+ end
end
end
diff --git a/public/images/preloader.svg b/public/images/preloader.svg
index 867d619ef60..b2319c1b2fb 100644
--- a/public/images/preloader.svg
+++ b/public/images/preloader.svg
@@ -12,6 +12,7 @@
--highlight: #f0ea88;
--quaternary: #65ccff;
--success: #009900;
+ --animation-state: paused;
}
/* these styles need to live here because the SVG has a different scope */
@@ -20,6 +21,7 @@
animation-timing-function: ease-in-out;
animation-duration: 3s;
animation-iteration-count: infinite;
+ animation-play-state: var(--animation-state);
stroke: #fff;
stroke-width: 0.5px;
transform-origin: center;
@@ -30,27 +32,26 @@
.dots:first-child {
fill: var(--tertiary);
- animation-delay: 0.625s;
}
.dots:nth-child(2) {
fill: var(--tertiary);
- animation-delay: 0.675s;
+ animation-delay: 0.15s;
}
.dots:nth-child(3) {
fill: var(--highlight);
- animation-delay: 0.725s;
+ animation-delay: 0.3s;
}
.dots:nth-child(4) {
fill: var(--quaternary);
- animation-delay: 0.775s;
+ animation-delay: 0.45s;
}
.dots:nth-child(5) {
fill: var(--quaternary);
- animation-delay: 0.825s;
+ animation-delay: 0.6s;
}
@keyframes loader {
diff --git a/spec/requests/application_controller_spec.rb b/spec/requests/application_controller_spec.rb
index a3cd36fda7b..d0a8b3148b9 100644
--- a/spec/requests/application_controller_spec.rb
+++ b/spec/requests/application_controller_spec.rb
@@ -665,6 +665,19 @@ RSpec.describe ApplicationController do
expect(response.body).to include(nonce)
end
+ it 'when splash screen is enabled it adds the same nonce to the policy and the inline splash script' do
+ SiteSetting.content_security_policy = true
+ SiteSetting.splash_screen = true
+
+ get '/latest'
+ nonce = ApplicationHelper.splash_screen_nonce
+ expect(response.headers).to include('Content-Security-Policy')
+
+ script_src = parse(response.headers['Content-Security-Policy'])['script-src']
+ expect(script_src.to_s).to include(nonce)
+ expect(response.body).to include(nonce)
+ end
+
def parse(csp_string)
csp_string.split(';').map do |policy|
directive, *sources = policy.split