From 85049f528c2626509d26d29813d9ec33f52a1271 Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Fri, 29 Aug 2025 17:02:33 +0100 Subject: [PATCH 01/13] Update peer dependency versionss --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eeb94ca..9b0e760 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "stylelint-prettier": "4.0.2" }, "peerDependencies": { - "@plone/volto": "^17.0.0" + "@plone/volto": "^16.0.0 || ^17.0.0" }, "browser": { "crypto": false From 670f9acd00d1641d900e6c06899ed370e8fd8aeb Mon Sep 17 00:00:00 2001 From: Jon Pentland Date: Tue, 14 Oct 2025 15:53:39 +0100 Subject: [PATCH 02/13] Shadow & patch injectStylesIntoStyleTag for nonce support in dev mode --- razzle.extend.js | 26 +++ src/injectStylesIntoStyleTag.js | 279 +++++++++++++++++++++++ src/injectStylesIntoStyleTag.original.js | 269 ++++++++++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 razzle.extend.js create mode 100644 src/injectStylesIntoStyleTag.js create mode 100644 src/injectStylesIntoStyleTag.original.js diff --git a/razzle.extend.js b/razzle.extend.js new file mode 100644 index 0000000..7a7fad5 --- /dev/null +++ b/razzle.extend.js @@ -0,0 +1,26 @@ +const path = require('path'); +const webpack = require('webpack'); + +const modify = (config, { target, dev }) => { + + // Only modify client-side builds in development + if (target === 'web' && dev) { + console.log('[volto-csp] Adding CSP nonce support for style-loader'); + + // Use NormalModuleReplacementPlugin to replace style-loader's runtime + // with our patched version that checks window.__webpack_nonce__ + config.plugins.push( + new webpack.NormalModuleReplacementPlugin( + /style-loader\/dist\/runtime\/injectStylesIntoStyleTag\.js$/, + path.resolve(__dirname, './src/injectStylesIntoStyleTag.js') + ) + ); + } + + return config; +}; + +module.exports = { + plugins: (plugs) => plugs, + modify, +}; diff --git a/src/injectStylesIntoStyleTag.js b/src/injectStylesIntoStyleTag.js new file mode 100644 index 0000000..a293797 --- /dev/null +++ b/src/injectStylesIntoStyleTag.js @@ -0,0 +1,279 @@ +// This is a patched version of: +// style-loader/dist/runtime/injectStylesIntoStyleTag.js +// which is injected in razzle.config.js + +"use strict"; + +var isOldIE = function isOldIE() { + var memo; + return function memorize() { + if (typeof memo === 'undefined') { + // Test for IE <= 9 as proposed by Browserhacks + // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805 + // Tests for existence of standard globals is to allow style-loader + // to operate correctly into non-standard environments + // @see https://github.com/webpack-contrib/style-loader/issues/177 + memo = Boolean(window && document && document.all && !window.atob); + } + + return memo; + }; +}(); + +var getTarget = function getTarget() { + var memo = {}; + return function memorize(target) { + if (typeof memo[target] === 'undefined') { + var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself + + if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) { + try { + // This will throw an exception if access to iframe is blocked + // due to cross-origin restrictions + styleTarget = styleTarget.contentDocument.head; + } catch (e) { + // istanbul ignore next + styleTarget = null; + } + } + + memo[target] = styleTarget; + } + + return memo[target]; + }; +}(); + +var stylesInDom = []; + +function getIndexByIdentifier(identifier) { + var result = -1; + + for (var i = 0; i < stylesInDom.length; i++) { + if (stylesInDom[i].identifier === identifier) { + result = i; + break; + } + } + + return result; +} + +function modulesToDom(list, options) { + var idCountMap = {}; + var identifiers = []; + + for (var i = 0; i < list.length; i++) { + var item = list[i]; + var id = options.base ? item[0] + options.base : item[0]; + var count = idCountMap[id] || 0; + var identifier = "".concat(id, " ").concat(count); + idCountMap[id] = count + 1; + var index = getIndexByIdentifier(identifier); + var obj = { + css: item[1], + media: item[2], + sourceMap: item[3] + }; + + if (index !== -1) { + stylesInDom[index].references++; + stylesInDom[index].updater(obj); + } else { + stylesInDom.push({ + identifier: identifier, + updater: addStyle(obj, options), + references: 1 + }); + } + + identifiers.push(identifier); + } + + return identifiers; +} + +function insertStyleElement(options) { + var style = document.createElement('style'); + var attributes = options.attributes || {}; + + // CSP nonce support - patched to check window.__webpack_nonce__ as fallback + if (typeof attributes.nonce === 'undefined') { + var nonce = typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null; + + // Fallback to window.__webpack_nonce__ if bare global not accessible + if (!nonce && typeof window !== 'undefined' && window.__webpack_nonce__) { + nonce = window.__webpack_nonce__; + } + + if (nonce) { + attributes.nonce = nonce; + } + } + + Object.keys(attributes).forEach(function (key) { + style.setAttribute(key, attributes[key]); + }); + + if (typeof options.insert === 'function') { + options.insert(style); + } else { + var target = getTarget(options.insert || 'head'); + + if (!target) { + throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."); + } + + target.appendChild(style); + } + + return style; +} + +function removeStyleElement(style) { + // istanbul ignore if + if (style.parentNode === null) { + return false; + } + + style.parentNode.removeChild(style); +} +/* istanbul ignore next */ + + +var replaceText = function replaceText() { + var textStore = []; + return function replace(index, replacement) { + textStore[index] = replacement; + return textStore.filter(Boolean).join('\n'); + }; +}(); + +function applyToSingletonTag(style, index, remove, obj) { + var css = remove ? '' : obj.media ? "@media ".concat(obj.media, " {").concat(obj.css, "}") : obj.css; // For old IE + + /* istanbul ignore if */ + + if (style.styleSheet) { + style.styleSheet.cssText = replaceText(index, css); + } else { + var cssNode = document.createTextNode(css); + var childNodes = style.childNodes; + + if (childNodes[index]) { + style.removeChild(childNodes[index]); + } + + if (childNodes.length) { + style.insertBefore(cssNode, childNodes[index]); + } else { + style.appendChild(cssNode); + } + } +} + +function applyToTag(style, options, obj) { + var css = obj.css; + var media = obj.media; + var sourceMap = obj.sourceMap; + + if (media) { + style.setAttribute('media', media); + } else { + style.removeAttribute('media'); + } + + if (sourceMap && typeof btoa !== 'undefined') { + css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */"); + } // For old IE + + /* istanbul ignore if */ + + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + while (style.firstChild) { + style.removeChild(style.firstChild); + } + + style.appendChild(document.createTextNode(css)); + } +} + +var singleton = null; +var singletonCounter = 0; + +function addStyle(obj, options) { + var style; + var update; + var remove; + + if (options.singleton) { + var styleIndex = singletonCounter++; + style = singleton || (singleton = insertStyleElement(options)); + update = applyToSingletonTag.bind(null, style, styleIndex, false); + remove = applyToSingletonTag.bind(null, style, styleIndex, true); + } else { + style = insertStyleElement(options); + update = applyToTag.bind(null, style, options); + + remove = function remove() { + removeStyleElement(style); + }; + } + + update(obj); + return function updateStyle(newObj) { + if (newObj) { + if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) { + return; + } + + update(obj = newObj); + } else { + remove(); + } + }; +} + +module.exports = function (list, options) { + options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of