Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: keep track of css injected to document (#11635) (#11673) #11793

Merged
merged 1 commit into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"));
}
}