From 21ef8ade0db5d3ff1bf5ba0edee28693743ec55c Mon Sep 17 00:00:00 2001 From: "shaurya.sisodia" Date: Tue, 21 Jun 2022 10:27:43 +0530 Subject: [PATCH 1/3] Fixed CSP Strict Style Compatibility Fixes #2355 Plotly uses inline CSS * Providing switch in build process to enable CSP strict style build * Creating a static CSS file at command line provided path defaulting to build folder with name plot-csp.css --- package.json | 2 +- tasks/preprocess.js | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6da16903681..2bf8eccd947 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tasks/preprocess.js b/tasks/preprocess.js index 8fc6e9a75f2..beb8d0fc23c 100644 --- a/tasks/preprocess.js +++ b/tasks/preprocess.js @@ -21,8 +21,17 @@ function makeBuildCSS() { }, function(err, result) { if(err) throw err; - // css to js - pullCSS(String(result.css), constants.pathToCSSBuild); + var cspNoInlineStyle = process.argv[2]; + var pathToCSS = process.argv[3] || 'plot-csp.css'; + if(cspNoInlineStyle) { + // if csp no inline style then build css file to include at path relative to build folder + fs.writeFile(constants.pathToBuild + pathToCSS, String(result.css), function(err) { + if(err) throw err; + }); + } else { + // css to js + pullCSS(String(result.css), constants.pathToCSSBuild); + } }); } From f17c0f706477b19ee05bbe9c8199e898cac2ed1f Mon Sep 17 00:00:00 2001 From: "shaurya.sisodia" Date: Tue, 21 Jun 2022 11:11:10 +0530 Subject: [PATCH 2/3] fixing env variable in preprocess --- tasks/preprocess.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks/preprocess.js b/tasks/preprocess.js index beb8d0fc23c..f466148ef21 100644 --- a/tasks/preprocess.js +++ b/tasks/preprocess.js @@ -21,11 +21,11 @@ function makeBuildCSS() { }, function(err, result) { if(err) throw err; - var cspNoInlineStyle = process.argv[2]; - var pathToCSS = process.argv[3] || 'plot-csp.css'; + var cspNoInlineStyle = process.env.npm_config_cspNoInlineStyle; + var pathToCSS = process.env.npm_config_pathToCSS || 'plot-csp.css'; if(cspNoInlineStyle) { - // if csp no inline style then build css file to include at path relative to build folder - fs.writeFile(constants.pathToBuild + pathToCSS, String(result.css), function(err) { + // if csp no inline style then build css file to include at path relative to dist folder + fs.writeFile(constants.pathToDist + pathToCSS, String(result.css), function(err) { if(err) throw err; }); } else { From 3e6a658593a2ccc24dc73f5002e937e7a626b9b4 Mon Sep 17 00:00:00 2001 From: "shaurya.sisodia" Date: Tue, 12 Jul 2022 15:41:49 +0530 Subject: [PATCH 3/3] missing commit --- src/components/modebar/modebar.js | 20 ++++++++--- src/lib/dom.js | 55 +++++++++++++++++++++++++++++++ src/lib/index.js | 4 +++ src/registry.js | 5 ++- tasks/preprocess.js | 26 +++++++++++++-- 5 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/components/modebar/modebar.js b/src/components/modebar/modebar.js index 35eec9fc6a6..86557f74d0a 100644 --- a/src/components/modebar/modebar.js +++ b/src/components/modebar/modebar.js @@ -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(); /** @@ -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); diff --git a/src/lib/dom.js b/src/lib/dom.js index 6a13e80c188..97ade34c92b 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -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 @@ -161,6 +214,8 @@ module.exports = { addStyleRule: addStyleRule, addRelatedStyleRule: addRelatedStyleRule, deleteRelatedStyleRule: deleteRelatedStyleRule, + setStyleOnElements: setStyleOnElements, + setStyleOnElementForEvent: setStyleOnElementForEvent, getFullTransformMatrix: getFullTransformMatrix, getElementTransformMatrix: getElementTransformMatrix, getElementAndAncestors: getElementAndAncestors, diff --git a/src/lib/index.js b/src/lib/index.js index 5d97f2cfc89..b2cb1e637d8 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -12,6 +12,8 @@ var BADNUM = numConstants.BADNUM; var lib = module.exports = {}; +lib.cspNoInlineStyle = false; + lib.adjustFormat = function adjustFormat(formatStr) { if( !formatStr || @@ -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; diff --git a/src/registry.js b/src/registry.js index 20039e99a97..d653f887268 100644 --- a/src/registry.js +++ b/src/registry.js @@ -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; @@ -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]); diff --git a/tasks/preprocess.js b/tasks/preprocess.js index f466148ef21..adaac3dc909 100644 --- a/tasks/preprocess.js +++ b/tasks/preprocess.js @@ -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'); @@ -13,7 +14,8 @@ 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, @@ -24,8 +26,24 @@ function makeBuildCSS() { 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, String(result.css), function(err) { + 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 { @@ -35,6 +53,10 @@ function makeBuildCSS() { }); } +function addAdditionalCSSRules(staticStyleString, selector, style) { + return staticStyleString + selector + '{' + style + '}'; +} + function exposePartsInLib() { var obj = {};