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 @@ <%=SiteSetting.title%> @@ -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