diff --git a/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js index fc6696481f8..8f3d3a9e56d 100644 --- a/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js @@ -83,7 +83,13 @@ const createLinkReferences = (css, target) => { const injectGlobalCssMethod = ` // target: Document | ShadowRoot export const injectGlobalCss = (css, target, first) => { - + if(target === document) { + const hash = getHash(css); + if (window.Vaadin.Flow.injectedGlobalCss.indexOf(hash) !== -1) { + return; + } + window.Vaadin.Flow.injectedGlobalCss.push(hash); + } const sheet = new CSSStyleSheet(); sheet.replaceSync(createLinkReferences(css,target)); if (first) { @@ -254,18 +260,48 @@ function generateThemeFile(themeFolder, themeName, themeProperties, productionMo themeFile += imports.join(''); themeFile += ` window.Vaadin = window.Vaadin || {}; -window.Vaadin['${globalCssFlag}'] = window.Vaadin['${globalCssFlag}'] || []; +window.Vaadin.Flow.injectedGlobalCss = []; + +/** + * Calculate a 32 bit FNV-1a hash + * Found here: https://gist.github.com/vaiorabbit/5657561 + * Ref.: http://isthe.com/chongo/tech/comp/fnv/ + * + * @param {string} str the input value + * @returns {string} 32 bit (as 8 byte hex string) + */ +function hashFnv32a(str) { + /*jshint bitwise:false */ + let i, l, hval = 0x811c9dc5; + + for (i = 0, l = str.length; i < l; i++) { + hval ^= str.charCodeAt(i); + hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); + } + + // Convert to 8 digit hex string + return ("0000000" + (hval >>> 0).toString(16)).substr(-8); +} + +/** + * Calculate a 64 bit hash for the given input. + * Double hash is used to significantly lower the collision probability. + * + * @param {string} input value to get hash for + * @returns {string} 64 bit (as 16 byte hex string) + */ +function getHash(input) { + let h1 = hashFnv32a(input); // returns 32 bit (as 8 byte hex string) + return h1 + hashFnv32a(h1 + input); +} `; // Don't format as the generated file formatting will get wonky! // If targets check that we only register the style parts once, checks exist for global css and component css const themeFileApply = `export const applyTheme = (target) => { ${parentTheme} - const injectGlobal = (window.Vaadin['${globalCssFlag}'].length === 0) || (!window.Vaadin['${globalCssFlag}'].includes(target) && target !== document); - if (injectGlobal) { - ${globalCssCode.join('')} - window.Vaadin['${globalCssFlag}'].push(target); - } + ${globalCssCode.join('')} + if (!document['${componentCssFlag}']) { ${componentCssCode.join('')} document['${componentCssFlag}'] = true; diff --git a/flow-tests/test-embedding/test-embedding-application-theme/src/test/java/com/vaadin/flow/webcomponent/ApplicationThemeComponentIT.java b/flow-tests/test-embedding/test-embedding-application-theme/src/test/java/com/vaadin/flow/webcomponent/ApplicationThemeComponentIT.java index 3e36147a998..f4cf9e4796b 100644 --- a/flow-tests/test-embedding/test-embedding-application-theme/src/test/java/com/vaadin/flow/webcomponent/ApplicationThemeComponentIT.java +++ b/flow-tests/test-embedding/test-embedding-application-theme/src/test/java/com/vaadin/flow/webcomponent/ApplicationThemeComponentIT.java @@ -221,4 +221,18 @@ public void stylesCssImport_externalLinkAddedToShadowroot() { Assert.assertTrue("Missing link for external url", linkUrls .contains("https://fonts.googleapis.com/css?family=Itim")); } + + @Test + public void multipleSameEmbedded_cssTargetingDocumentShouldOnlyAddElementsOneTime() { + open(); + checkLogsForErrors(); + Assert.assertEquals( + "document.css adds 2 font links and those should not duplicate", + 2l, getCommandExecutor().executeScript( + "return document.head.getElementsByTagName('link').length")); + Assert.assertEquals( + "Project contains 2 css injections to document and both should be hashed", + 2l, getCommandExecutor().executeScript( + "return window.Vaadin.Flow.injectedGlobalCss.length")); + } }