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

Move CSS in a separate file to be CSP-compliant #6048

Merged
merged 3 commits into from Feb 8, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/getting-started/integration.md
Expand Up @@ -84,3 +84,18 @@ require(['moment'], function() {
});
});
```

## Content Security Policy

By default, Chart.js injects CSS directly into the DOM. For webpages secured using [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), this requires to allow `style-src 'unsafe-inline'`. For stricter CSP environments, where only `style-src 'self'` is allowed, the following CSS file needs to be manually added to your webpage:

```html
<link rel="stylesheet" type="text/css" href="path/to/chartjs/dist/Chart.min.css">
```

And the style injection must be turned off **before creating the first chart**:
simonbrunel marked this conversation as resolved.
Show resolved Hide resolved

```javascript
// Disable automatic style injection
Chart.platform.disableCSSInjection = true;
```
2 changes: 1 addition & 1 deletion gulpfile.js
Expand Up @@ -86,7 +86,7 @@ function buildTask() {
function packageTask() {
return merge(
// gather "regular" files landing in the package root
gulp.src([outDir + '*.js', 'LICENSE.md']),
gulp.src([outDir + '*.js', outDir + '*.css', 'LICENSE.md']),

// since we moved the dist files one folder up (package root), we need to rewrite
// samples src="../dist/ to src="../ and then copy them in the /samples directory.
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -23,6 +23,7 @@
"url": "https://github.com/chartjs/Chart.js/issues"
},
"devDependencies": {
"clean-css": "^4.2.1",
"coveralls": "^3.0.0",
"eslint": "^5.9.0",
"eslint-config-chartjs": "^0.1.0",
Expand Down
14 changes: 13 additions & 1 deletion rollup.config.js
Expand Up @@ -4,6 +4,7 @@ const commonjs = require('rollup-plugin-commonjs');
const resolve = require('rollup-plugin-node-resolve');
const terser = require('rollup-plugin-terser').terser;
const optional = require('./rollup.plugins').optional;
const stylesheet = require('./rollup.plugins').stylesheet;
const pkg = require('./package.json');

const input = 'src/chart.js';
Expand All @@ -23,6 +24,9 @@ module.exports = [
plugins: [
resolve(),
commonjs(),
stylesheet({
extract: true
}),
optional({
include: ['moment']
})
Expand All @@ -49,6 +53,10 @@ module.exports = [
optional({
include: ['moment']
}),
stylesheet({
extract: true,
minify: true
}),
terser({
output: {
preamble: banner
Expand Down Expand Up @@ -76,7 +84,8 @@ module.exports = [
input: input,
plugins: [
resolve(),
commonjs()
commonjs(),
stylesheet()
],
output: {
name: 'Chart',
Expand All @@ -91,6 +100,9 @@ module.exports = [
plugins: [
resolve(),
commonjs(),
stylesheet({
minify: true
}),
terser({
output: {
preamble: banner
Expand Down
49 changes: 48 additions & 1 deletion rollup.plugins.js
@@ -1,4 +1,6 @@
/* eslint-env es6 */
const cleancss = require('clean-css');
const path = require('path');

const UMD_WRAPPER_RE = /(\(function \(global, factory\) \{)((?:\s.*?)*)(\}\(this,)/;
const CJS_FACTORY_RE = /(module.exports = )(factory\(.*?\))( :)/;
Expand Down Expand Up @@ -56,6 +58,51 @@ function optional(config = {}) {
};
}

// https://github.com/chartjs/Chart.js/issues/5208
function stylesheet(config = {}) {
const minifier = new cleancss();
const styles = [];

return {
name: 'stylesheet',
transform(code, id) {
// Note that 'id' can be mapped to a CJS proxy import, in which case
// 'id' will start with 'commonjs-proxy', so let's first check if we
// are importing an existing css file (i.e. startsWith()).
if (!id.startsWith(path.resolve('.')) || !id.endsWith('.css')) {
return;
}

if (config.minify) {
code = minifier.minify(code).styles;
}

// keep track of all imported stylesheets (already minified)
styles.push(code);

return {
code: 'export default ' + JSON.stringify(code)
};
},
generateBundle(opts, bundle) {
if (!config.extract) {
return;
}

const entry = Object.keys(bundle).find(v => bundle[v].isEntry);
const name = (entry || '').replace(/\.js$/i, '.css');
if (!name) {
this.error('failed to guess the output file name');
}

bundle[name] = {
code: styles.filter(v => !!v).join('')
};
}
};
}

module.exports = {
optional
optional,
stylesheet
};
20 changes: 20 additions & 0 deletions samples/advanced/content-security-policy.css
@@ -0,0 +1,20 @@
.content {
max-width: 640px;
margin: auto;
padding: 1rem;
}

.note {
font-family: sans-serif;
color: #5050a0;
line-height: 1.4;
margin-bottom: 1rem;
padding: 1rem;
}

code {
background-color: #f5f5ff;
border: 1px solid #d0d0fa;
border-radius: 4px;
padding: 0.05rem 0.25rem;
}
27 changes: 27 additions & 0 deletions samples/advanced/content-security-policy.html
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<title>Scriptable > Bubble | Chart.js sample</title>
<link rel="stylesheet" type="text/css" href="../../dist/Chart.min.css">
<link rel="stylesheet" type="text/css" href="./content-security-policy.css">
simonbrunel marked this conversation as resolved.
Show resolved Hide resolved
<script src="../../dist/Chart.min.js"></script>
<script src="../utils.js"></script>
<script src="content-security-policy.js"></script>
</head>
<body>
<div class="content">
<div class="note">
In order to support a strict content security policy (<code>default-src 'self'</code>),
this page manually loads <code>Chart.min.css</code> and turns off the automatic style
injection by setting <code>Chart.platform.disableCSSInjection = true;</code>.
</div>
<div class="wrapper">
benmccann marked this conversation as resolved.
Show resolved Hide resolved
<canvas id="chart-0"></canvas>
</div>
</div>
</body>
</html>
54 changes: 54 additions & 0 deletions samples/advanced/content-security-policy.js
@@ -0,0 +1,54 @@
var utils = Samples.utils;

// CSP: disable automatic style injection
Chart.platform.disableCSSInjection = true;

utils.srand(110);

function generateData() {
var DATA_COUNT = 16;
var MIN_XY = -150;
var MAX_XY = 100;
var data = [];
var i;

for (i = 0; i < DATA_COUNT; ++i) {
data.push({
x: utils.rand(MIN_XY, MAX_XY),
y: utils.rand(MIN_XY, MAX_XY),
v: utils.rand(0, 1000)
});
}

return data;
}

window.addEventListener('load', function() {
new Chart('chart-0', {
type: 'bubble',
data: {
datasets: [{
backgroundColor: utils.color(0),
data: generateData()
}, {
backgroundColor: utils.color(1),
data: generateData()
}]
},
options: {
aspectRatio: 1,
legend: false,
tooltip: false,
elements: {
point: {
radius: function(context) {
var value = context.dataset.data[context.dataIndex];
var size = context.chart.width;
var base = Math.abs(value.v) / 1000;
return (size / 24) * base;
}
}
}
}
});
});
3 changes: 3 additions & 0 deletions samples/samples.js
Expand Up @@ -187,6 +187,9 @@
items: [{
title: 'Progress bar',
path: 'advanced/progress-bar.html'
}, {
title: 'Content Security Policy',
path: 'advanced/content-security-policy.html'
}]
}];

Expand Down
1 change: 0 additions & 1 deletion samples/style.css
@@ -1,4 +1,3 @@
@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css');
@import url('https://fonts.googleapis.com/css?family=Lato:100,300,400,700,900');

body, html {
Expand Down
1 change: 1 addition & 0 deletions scripts/deploy.sh
Expand Up @@ -41,6 +41,7 @@ cd $TARGET_DIR
git checkout $TARGET_BRANCH

# Copy dist files
deploy_files '../dist/*.css' './dist'
deploy_files '../dist/*.js' './dist'

# Copy generated documentation
Expand Down
2 changes: 1 addition & 1 deletion scripts/release.sh
Expand Up @@ -21,7 +21,7 @@ git remote add auth-origin https://$GITHUB_AUTH_TOKEN@github.com/$TRAVIS_REPO_SL
git config --global user.email "$GITHUB_AUTH_EMAIL"
git config --global user.name "Chart.js"
git checkout --detach --quiet
git add -f dist/*.js bower.json
git add -f dist/*.css dist/*.js bower.json
git commit -m "Release $VERSION"
git tag -a "v$VERSION" -m "Version $VERSION"
git push -q auth-origin refs/tags/v$VERSION 2>/dev/null
Expand Down
46 changes: 46 additions & 0 deletions src/platforms/platform.dom.css
@@ -0,0 +1,46 @@
/*
* DOM element rendering detection
* https://davidwalsh.name/detect-node-insertion
*/
@keyframes chartjs-render-animation {
from { opacity: 0.99; }
to { opacity: 1; }
}

.chartjs-render-monitor {
animation: chartjs-render-animation 0.001s;
}

/*
* DOM element resizing detection
* https://github.com/marcj/css-element-queries
*/
.chartjs-size-monitor,
.chartjs-size-monitor-expand,
.chartjs-size-monitor-shrink {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
pointer-events: none;
visibility: hidden;
z-index: -1;
}

.chartjs-size-monitor-expand > div {
position: absolute;
width: 1000000px;
height: 1000000px;
left: 0;
top: 0;
}

.chartjs-size-monitor-shrink > div {
position: absolute;
width: 200%;
height: 200%;
left: 0;
top: 0;
}