diff --git a/packages/core/src/asset_manager/view/AssetImageView.ts b/packages/core/src/asset_manager/view/AssetImageView.ts index 0853f9d4f..78f9a9be4 100644 --- a/packages/core/src/asset_manager/view/AssetImageView.ts +++ b/packages/core/src/asset_manager/view/AssetImageView.ts @@ -5,14 +5,23 @@ import html from '../../utils/html'; export default class AssetImageView extends AssetView { getPreview() { - const { pfx, ppfx, model } = this; - const src = model.get('src'); + const { pfx, ppfx } = this; return html` -
+
`; } + render() { + super.render(); + // Set through the CSSOM, a `style` attribute would be blocked by a strict + // `style-src-attr` policy + const previewEl = this.el.querySelector('[data-preview]') as HTMLElement; + const src = this.model.get('src'); + previewEl && previewEl.style.setProperty('background-image', src ? `url(${JSON.stringify(src)})` : ''); + return this; + } + getInfo() { const { pfx, model } = this; let name = model.get('name'); diff --git a/packages/core/src/asset_manager/view/AssetsView.ts b/packages/core/src/asset_manager/view/AssetsView.ts index a62638c00..b4dbc77f7 100644 --- a/packages/core/src/asset_manager/view/AssetsView.ts +++ b/packages/core/src/asset_manager/view/AssetsView.ts @@ -20,7 +20,7 @@ export default class AssetsView extends View { -
+
`; } @@ -31,7 +31,7 @@ export default class AssetsView extends View { ${form}
-
+
`; } diff --git a/packages/core/src/asset_manager/view/FileUploader.ts b/packages/core/src/asset_manager/view/FileUploader.ts index 900a19113..8744d9aa6 100644 --- a/packages/core/src/asset_manager/view/FileUploader.ts +++ b/packages/core/src/asset_manager/view/FileUploader.ts @@ -48,6 +48,7 @@ export default class FileUploaderView extends View { uploadForm?: HTMLFormElement | null; template({ pfx, title, uploadId, disabled, multiUpload }: FileUploaderTemplateProps) { + const { ppfx } = this; return html`
${title}
@@ -60,7 +61,7 @@ export default class FileUploaderView extends View { ${disabled ? 'disabled' : ''} ${multiUpload ? 'multiple' : ''} /> -
+
`; } diff --git a/packages/core/src/canvas/view/CanvasView.ts b/packages/core/src/canvas/view/CanvasView.ts index 6efaff60a..600bceb9a 100644 --- a/packages/core/src/canvas/view/CanvasView.ts +++ b/packages/core/src/canvas/view/CanvasView.ts @@ -5,6 +5,7 @@ import Component from '../../dom_components/model/Component'; import ComponentView from '../../dom_components/view/ComponentView'; import { createEl, + createStyleEl, getDocumentScroll, getElRect, getKeyChar, @@ -59,7 +60,6 @@ export default class CanvasView extends ModuleView {
- `; } /*get className(){ @@ -666,16 +666,16 @@ export default class CanvasView extends ModuleView { const toolsWrp = $el.find('[data-tools]'); this.toolsWrapper = toolsWrp.get(0); toolsWrp.append(` -
+
-
+
${config.extHl ? `
` : ''}
-
+
@@ -692,7 +692,10 @@ export default class CanvasView extends ModuleView { this.fixedOffsetEl = el.querySelector(`.${ppfx}offset-fixed-v`)!; this.toolsGlobEl = el.querySelector(`.${ppfx}tools-gl`)!; this.spotsEl = el.querySelector('[data-spots]')!; - this.cvStyle = el.querySelector('[data-canvas-style]')!; + // Created here instead of in the template, so that the CSP nonce is in + // place before the element enters the document + this.cvStyle = createStyleEl('', em.getConfig().cspNonce, { 'data-canvas-style': '' }); + el.appendChild(this.cvStyle); el.className = getUiClass(em, this.className); if (config.scrollableCanvas === true) { el.style.overflow = 'auto'; diff --git a/packages/core/src/canvas/view/FrameView.ts b/packages/core/src/canvas/view/FrameView.ts index 927ea56bb..f3f857bbf 100644 --- a/packages/core/src/canvas/view/FrameView.ts +++ b/packages/core/src/canvas/view/FrameView.ts @@ -7,7 +7,7 @@ import ComponentView from '../../dom_components/view/ComponentView'; import ComponentWrapperView from '../../dom_components/view/ComponentWrapperView'; import AutoScroller from '../../utils/AutoScroller'; import Droppable from '../../utils/Droppable'; -import { append, appendVNodes, createCustomEvent, createEl, motionsEv, off, on } from '../../utils/dom'; +import { append, appendVNodes, createCustomEvent, createEl, createStyleEl, motionsEv, off, on } from '../../utils/dom'; import { hasDnd, setViewEl } from '../../utils/mixins'; import Canvas from '../model/Canvas'; import Frame from '../model/Frame'; @@ -374,7 +374,8 @@ export default class FrameView extends ModuleView { append( body, - ``, + `, + conf.cspNonce, + ), ); const { root } = model; const { view } = this.getComponentView(root); diff --git a/packages/core/src/canvas/view/FrameWrapView.ts b/packages/core/src/canvas/view/FrameWrapView.ts index 1c9f97347..595a366cb 100644 --- a/packages/core/src/canvas/view/FrameWrapView.ts +++ b/packages/core/src/canvas/view/FrameWrapView.ts @@ -204,7 +204,7 @@ export default class FrameWrapView extends ModuleView { ${model.get('name') || ''}
- @@ -218,8 +218,7 @@ export default class FrameWrapView extends ModuleView { const elTools = createEl( 'div', { - class: `${ppfx}tools`, - style: 'pointer-events:none; display: none', + class: `${ppfx}tools ${ppfx}no-pointer-events`, }, `
@@ -228,7 +227,7 @@ export default class FrameWrapView extends ModuleView {
-
+
@@ -247,6 +246,9 @@ export default class FrameWrapView extends ModuleView {
`, ); + // Kept on the CSSOM instead of a class, `toggleToolsEl` shows it back by + // resetting the inline display + elTools.style.display = 'none'; this.elTools = elTools; const twrp = cv?.toolsWrapper; twrp && twrp.appendChild(elTools); // TODO remove on frame remove diff --git a/packages/core/src/commands/view/Preview.ts b/packages/core/src/commands/view/Preview.ts index 877753925..6335067f0 100644 --- a/packages/core/src/commands/view/Preview.ts +++ b/packages/core/src/commands/view/Preview.ts @@ -102,7 +102,9 @@ export default class CommandPreview extends CommandAbstract { panels.forEach((panel) => panel.set('visible', true)); const canvas = editor.Canvas.getElement(); - canvas.setAttribute('style', ''); + // Removing beats writing an empty `style`, which a strict `style-src-attr` + // policy would still report + canvas.removeAttribute('style'); selected && editor.select(selected); delete this.selected; diff --git a/packages/core/src/css_composer/view/CssRuleView.ts b/packages/core/src/css_composer/view/CssRuleView.ts index 903127437..4c2d63ffe 100644 --- a/packages/core/src/css_composer/view/CssRuleView.ts +++ b/packages/core/src/css_composer/view/CssRuleView.ts @@ -1,6 +1,7 @@ import FrameView from '../../canvas/view/FrameView'; import { View } from '../../common'; import EditorModel from '../../editor/model/Editor'; +import { setNonce } from '../../utils/dom'; import CssRule from '../model/CssRule'; import { CssEvents } from '../types'; @@ -46,6 +47,9 @@ export default class CssRuleView extends View { const css = model.toCSS({ important }); const mountProps = { rule: model, ruleView: this, css }; em?.trigger(CssEvents.mountBefore, mountProps); + // Has to be set before the style block is filled, otherwise a strict + // `style-src` policy blocks the rule + setNonce(el as HTMLElement, em?.getConfig().cspNonce); el.innerHTML = mountProps.css; em?.trigger(CssEvents.mount, mountProps); return this; diff --git a/packages/core/src/css_composer/view/CssRulesView.ts b/packages/core/src/css_composer/view/CssRulesView.ts index af65d255f..a01421af9 100644 --- a/packages/core/src/css_composer/view/CssRulesView.ts +++ b/packages/core/src/css_composer/view/CssRulesView.ts @@ -1,7 +1,7 @@ import { bindAll } from 'underscore'; import { View } from '../../common'; -import { createEl } from '../../utils/dom'; +import { createEl, createStyleEl } from '../../utils/dom'; import CssRuleView from './CssRuleView'; import CssGroupRuleView from './CssGroupRuleView'; import EditorModel from '../../editor/model/Editor'; @@ -66,7 +66,7 @@ export default class CssRulesView extends View { let atRuleEl = this.atRules[atRule]; if (!atRuleEl) { - const styleEl = document.createElement('style'); + const styleEl = createStyleEl('', this.em?.getConfig().cspNonce); atRuleEl = document.createTextNode(''); styleEl.appendChild(document.createTextNode(`${atRule}{`)); styleEl.appendChild(atRuleEl); diff --git a/packages/core/src/device_manager/view/DevicesView.ts b/packages/core/src/device_manager/view/DevicesView.ts index 4ec0c5a67..f826ef1c3 100644 --- a/packages/core/src/device_manager/view/DevicesView.ts +++ b/packages/core/src/device_manager/view/DevicesView.ts @@ -25,7 +25,7 @@ export default class DevicesView extends View {
- + `; } diff --git a/packages/core/src/dom_components/view/ComponentFrameView.ts b/packages/core/src/dom_components/view/ComponentFrameView.ts index 7efc9e49e..70a3a0b3a 100644 --- a/packages/core/src/dom_components/view/ComponentFrameView.ts +++ b/packages/core/src/dom_components/view/ComponentFrameView.ts @@ -1,5 +1,5 @@ import ComponentView from './ComponentView'; -import { createEl, find, attrUp } from '../../utils/dom'; +import { createEl, find, attrUp, setStyleText } from '../../utils/dom'; import ComponentFrame from '../model/ComponentFrame'; export default class ComponentFrameView extends ComponentView { @@ -21,9 +21,11 @@ export default class ComponentFrameView extends ComponentView { super.render(); const frame = createEl('iframe', { class: `${this.ppfx}no-pointer`, - style: 'width: 100%; height: 100%; border: none', src: this.__getSrc(), }); + // Set through the CSSOM, a `style` attribute would be blocked by a strict + // `style-src-attr` policy + setStyleText(frame, 'width: 100%; height: 100%; border: none'); this.el.appendChild(frame); return this; } diff --git a/packages/core/src/editor/config/config.ts b/packages/core/src/editor/config/config.ts index db5857aee..3e48a1ec6 100644 --- a/packages/core/src/editor/config/config.ts +++ b/packages/core/src/editor/config/config.ts @@ -244,6 +244,21 @@ export interface EditorConfig { */ avoidInlineStyle?: boolean; + /** + * CSP nonce to set on every `