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 CSP Strict Style Compatibility #6239

Closed
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"schema": "node tasks/schema.js",
"stats": "node tasks/stats.js",
"find-strings": "node tasks/find_locale_strings.js",
"preprocess": "node tasks/preprocess.js",
"preprocess": "node tasks/preprocess.js %npm_config_cspNoInlineStyle% %npm_config_pathToCSS%",
"use-draftlogs": "node tasks/use_draftlogs.js",
"empty-draftlogs": "node tasks/empty_draftlogs.js",
"empty-dist": "node tasks/empty_dist.js",
Expand Down
20 changes: 15 additions & 5 deletions src/components/modebar/modebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ var Lib = require('../../lib');
var Icons = require('../../fonts/ploticon');
var version = require('../../version').version;

var cspNoInlineStyle = require('./../../lib').cspNoInlineStyle;

var Parser = new DOMParser();

/**
Expand Down Expand Up @@ -56,11 +58,19 @@ proto.update = function(graphInfo, buttons) {
var style = fullLayout.modebar;
var bgSelector = context.displayModeBar === 'hover' ? '.js-plotly-plot .plotly:hover ' : '';

Lib.deleteRelatedStyleRule(modeBarId);
Lib.addRelatedStyleRule(modeBarId, bgSelector + '#' + modeBarId + ' .modebar-group', 'background-color: ' + style.bgcolor);
Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn .icon path', 'fill: ' + style.color);
Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn:hover .icon path', 'fill: ' + style.activecolor);
Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn.active .icon path', 'fill: ' + style.activecolor);
if(cspNoInlineStyle) {
// set style rules on elements in csp strict style src compliant manner
Lib.setStyleOnElements(bgSelector + '#' + modeBarId + ' .modebar-group', 'background-color: ' + style.bgcolor);
Lib.setStyleOnElements('#' + modeBarId + ' .modebar-btn .icon path', 'fill: ' + style.color);
Lib.setStyleOnElementForEvent('#' + modeBarId + ' .modebar-btn', 'hover', '.icon path', 'fill: ' + style.activecolor, 'fill: ' + style.color);
Lib.setStyleOnElementForEvent('#' + modeBarId + ' .modebar-btn', 'active', '.icon path', 'fill: ' + style.activecolor, 'fill: ' + style.color);
} else {
Lib.deleteRelatedStyleRule(modeBarId);
Lib.addRelatedStyleRule(modeBarId, bgSelector + '#' + modeBarId + ' .modebar-group', 'background-color: ' + style.bgcolor);
Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn .icon path', 'fill: ' + style.color);
Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn:hover .icon path', 'fill: ' + style.activecolor);
Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn.active .icon path', 'fill: ' + style.activecolor);
}

// if buttons or logo have changed, redraw modebar interior
var needsNewButtons = !this.hasButtons(buttons);
Expand Down
55 changes: 55 additions & 0 deletions src/lib/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,59 @@ function deleteRelatedStyleRule(uid) {
if(style) removeElement(style);
}

/**
* to set style directly on elements in CSP Strict style compatible way
*/
function setStyleOnElements(selector, style) {
var styleRule = style.split(':');
document.querySelectorAll(selector).forEach(function(el) {
// don't update style for active element, as activated state styling is separately handled
if(document.activeElement !== el) {
el.style[styleRule[0]] = styleRule[1];
}
});
}

/**
* set style on element for 'hover' and 'active' state
* this method is in CSP strict style source mode instead of addStyleRule which is not CSP compliant
* @param {string} selector selector on which to listen to event
* @param {string} state 'hover' or 'active' state
* @param {string} childSelector the child element on which the styling needs to be updated
* @param {string} style style that has to be applied on 'hover' or 'active' state
* @param {string} fallbackStyle style that has to be applied when we revert from the state
*/
function setStyleOnElementForEvent(selector, state, childSelector, style, fallbackStyle) {
var styleRule = style.split(':');
var activationEvent;
var deactivationEvent;
switch(state) {
case 'hover':
activationEvent = 'mouseenter';
deactivationEvent = 'mouseleave';
break;
case 'active':
activationEvent = 'mousedown';
deactivationEvent = '';
break;
}

document.querySelectorAll(selector).forEach(function(el) {
if(activationEvent && !el.getAttribute(activationEvent + 'eventAdded')) {
el.addEventListener(activationEvent, function() {
el.querySelector(childSelector).style[styleRule[0]] = styleRule[1];
});
el.setAttribute(activationEvent + 'eventAdded', true);
}
if(deactivationEvent && !el.getAttribute(deactivationEvent + 'eventAdded')) {
el.addEventListener(deactivationEvent, function() {
el.querySelector(childSelector).style[styleRule[0]] = fallbackStyle.split(':')[1];
});
el.setAttribute(deactivationEvent + 'eventAdded', true);
}
});
}

function getFullTransformMatrix(element) {
var allElements = getElementAndAncestors(element);
// the identity matrix
Expand Down Expand Up @@ -161,6 +214,8 @@ module.exports = {
addStyleRule: addStyleRule,
addRelatedStyleRule: addRelatedStyleRule,
deleteRelatedStyleRule: deleteRelatedStyleRule,
setStyleOnElements: setStyleOnElements,
setStyleOnElementForEvent: setStyleOnElementForEvent,
getFullTransformMatrix: getFullTransformMatrix,
getElementTransformMatrix: getElementTransformMatrix,
getElementAndAncestors: getElementAndAncestors,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var BADNUM = numConstants.BADNUM;

var lib = module.exports = {};

lib.cspNoInlineStyle = false;

lib.adjustFormat = function adjustFormat(formatStr) {
if(
!formatStr ||
Expand Down Expand Up @@ -187,6 +189,8 @@ lib.removeElement = domModule.removeElement;
lib.addStyleRule = domModule.addStyleRule;
lib.addRelatedStyleRule = domModule.addRelatedStyleRule;
lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule;
lib.setStyleOnElements = domModule.setStyleOnElements;
lib.setStyleOnElementForEvent = domModule.setStyleOnElementForEvent;
lib.getFullTransformMatrix = domModule.getFullTransformMatrix;
lib.getElementTransformMatrix = domModule.getElementTransformMatrix;
lib.getElementAndAncestors = domModule.getElementAndAncestors;
Expand Down
5 changes: 4 additions & 1 deletion src/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ var ExtendModule = require('./lib/extend');
var basePlotAttributes = require('./plots/attributes');
var baseLayoutAttributes = require('./plots/layout_attributes');

var cspNoInlineStyle = require('./lib').cspNoInlineStyle;

var extendFlat = ExtendModule.extendFlat;
var extendDeepAll = ExtendModule.extendDeepAll;

Expand Down Expand Up @@ -265,7 +267,8 @@ function registerTraceModule(_module) {
var bpmName = basePlotModule.name;

// add mapbox-gl CSS here to avoid console warning on instantiation
if(bpmName === 'mapbox') {
// in case of cspNoInlineStyle we will add the styles in a static style sheet during preprocess task
if(bpmName === 'mapbox' && !cspNoInlineStyle) {
var styleRules = basePlotModule.constants.styleRules;
for(var k in styleRules) {
addStyleRule('.js-plotly-plot .plotly .mapboxgl-' + k, styleRules[k]);
Expand Down
37 changes: 34 additions & 3 deletions tasks/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var path = require('path');
var sass = require('sass');

var constants = require('./util/constants');
var mapBoxGLStyleRules = require('./../src/plots/mapbox/constants').styleRules;
var common = require('./util/common');
var pullCSS = require('./util/pull_css');
var updateVersion = require('./util/update_version');
Expand All @@ -13,19 +14,49 @@ exposePartsInLib();
copyTopojsonFiles();
updateVersion(constants.pathToPlotlyVersion);

// convert scss to css to js
// if no csp: convert scss to css to js
// if csp: convert scss to css
function makeBuildCSS() {
sass.render({
file: constants.pathToSCSS,
outputStyle: 'compressed'
}, function(err, result) {
if(err) throw err;

// css to js
pullCSS(String(result.css), constants.pathToCSSBuild);
var cspNoInlineStyle = process.env.npm_config_cspNoInlineStyle;
var pathToCSS = process.env.npm_config_pathToCSS || 'plot-csp.css';
if(cspNoInlineStyle) {
var staticCSS = String(result.css);
for(var k in mapBoxGLStyleRules) {
staticCSS = addAdditionalCSSRules(staticCSS, '.js-plotly-plot .plotly .mapboxgl-' + k, mapBoxGLStyleRules[k]);
}

// if csp no inline style then build css file to include at path relative to dist folder
fs.writeFile(constants.pathToDist + pathToCSS, staticCSS, function(err) {
if(err) throw err;
});

// use plotcss.js to set global cspNoInlineStyle as true
var outStr = ['\'use strict\';',
'',
'var Lib = require(\'../src/lib\');',
'Lib.cspNoInlineStyle = true;',
''].join('\n');

fs.writeFile(constants.pathToCSSBuild, outStr, function(err) {
if(err) throw err;
});
} else {
// css to js
pullCSS(String(result.css), constants.pathToCSSBuild);
}
});
}

function addAdditionalCSSRules(staticStyleString, selector, style) {
return staticStyleString + selector + '{' + style + '}';
}

function exposePartsInLib() {
var obj = {};

Expand Down