Skip to content

Commit

Permalink
fix: keep track of css injected to document (#11635) (#11673) (#11793)
Browse files Browse the repository at this point in the history
Stops css/fonts from being loaded multiple times to the document with embedded applications.

Fixes #11608
  • Loading branch information
caalador authored Sep 9, 2021
1 parent 750ed9d commit 99a1a54
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ protected static class BootstrapContext {
private JsonObject applicationParameters;
private BootstrapUriResolver uriResolver;

private boolean initTheme = true;

/**
* Creates a new context instance using the given parameters.
*
Expand Down Expand Up @@ -237,6 +239,22 @@ public VaadinSession getSession() {
return session;
}

/**
* Should custom theme be initialized.
* @return true if theme should be initialized
*/
public boolean isInitTheme() {
return initTheme;
}

/**
* Set if custom theme should be initialized.
* @param initTheme enable or disable theme initialisation
*/
public void setInitTheme(boolean initTheme) {
this.initTheme = initTheme;
}

/**
* Gets the UI.
*
Expand Down Expand Up @@ -567,6 +585,15 @@ public Document getBootstrapPage(BootstrapContext context) {
handleThemeContents(context, document);
}

// To not init theme for webcomponents we use a flag on the dom
if (context.isInitTheme() && context.getTheme().isPresent()
&& !"".equals(context.getTheme().get().getName())) {
head.prependElement("script").attr("type", "text/javascript")
.appendChild(new DataNode(
"window.Vaadin = window.Vaadin || {}; window.Vaadin.theme = window.Vaadin.theme || {};"
+ "window.Vaadin.theme.flowBootstrap = true;"));
}

if (!config.isProductionMode()) {
if (config.isBowerMode()) {
exportBowerUsageStatistics(document);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private WebComponentBootstrapContext(VaadinRequest request,
Function<VaadinRequest, String> callback) {
super(request, response, ui.getInternals().getSession(), ui,
callback);
setInitTheme(false);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,11 @@ protected Collection<String> getThemeLines() {

if (hasApplicationTheme) {
// If we define a theme name we need to import
// theme/theme-generated.js
// theme/theme.js on flow bootstrap
lines.add(
"import {applyTheme} from 'themes/theme-generated.js';");
lines.add("applyTheme(document);");
"import {applyTheme} from 'themes/theme-generated.js';");
lines.add(
"if(window.Vaadin.theme.flowBootstrap) applyTheme(document);");
}

if (theme != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,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.theme.injectedGlobalCss.indexOf(hash) !== -1) {
return;
}
window.Vaadin.theme.injectedGlobalCss.push(hash);
}
const sheet = new CSSStyleSheet();
sheet.replaceSync(createLinkReferences(css,target));
if (first) {
Expand Down Expand Up @@ -151,7 +157,6 @@ function generateThemeFile(themeFolder, themeName, themeProperties, productionMo

const themeIdentifier = '_vaadintheme_' + themeName + '_';
const lumoCssFlag = '_vaadinthemelumoimports_';
const globalCssFlag = themeIdentifier + 'globalCss';
const componentCssFlag = themeIdentifier + 'componentCss';

if (!fs.existsSync(styles)) {
Expand Down Expand Up @@ -252,20 +257,50 @@ function generateThemeFile(themeFolder, themeName, themeProperties, productionMo

themeFile += imports.join('');
themeFile += `
window.Vaadin = window.Vaadin || {};
window.Vaadin.Flow = window.Vaadin.Flow || {};
window.Vaadin.Flow['${globalCssFlag}'] = window.Vaadin.Flow['${globalCssFlag}'] || [];
window.Vaadin = window.Vaadin || {};
window.Vaadin.theme = window.Vaadin.theme || {};
window.Vaadin.theme.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.Flow['${globalCssFlag}'].length === 0) || (!window.Vaadin.Flow['${globalCssFlag}'].includes(target) && target !== document);
if (injectGlobal) {
${globalCssCode.join('')}
window.Vaadin.Flow['${globalCssFlag}'].push(target);
}
${globalCssCode.join('')}
if (!document['${componentCssFlag}']) {
${componentCssCode.join('')}
document['${componentCssFlag}'] = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,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.theme.injectedGlobalCss.length"));
}
}

0 comments on commit 99a1a54

Please sign in to comment.