Skip to content

Commit

Permalink
chore: add CSP header example, backported from PR #884 (#891)
Browse files Browse the repository at this point in the history
* chore: add CSP header example
- ref issue #878

* chore: add DOMPurify to CSP example

* fix: move `innerHTML` as separate assignment outside of createDomElement
- to further improve CSP support for issue #878, we need to move `innerHTML` as separate assignment and not use it directly within a `createDomElement`, so for example this line `const elm = createDomElement('div', { innerHTML: '' })` should be split in 2 lines `const elm = createDomElement('div'); elm.innerHTML = '';`
  • Loading branch information
ghiscoding authored Oct 30, 2023
1 parent ada4690 commit e722456
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 9 deletions.
1 change: 1 addition & 0 deletions cypress/integration/example-colspan.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('Example - Column Span & Header Grouping', { retries: 1 }, () => {
it('should display Example title', () => {
cy.visit(`${Cypress.config('baseExampleUrl')}/example-colspan.html`);
cy.get('h2').contains('Demonstrates');
cy.get('h2 + ul > li').first().contains('column span');
});

it('should have exact column titles', () => {
Expand Down
52 changes: 52 additions & 0 deletions cypress/integration/example-csp-header.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
describe('Example CSP Header - with Column Span & Header Grouping', () => {
// NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows
const GRID_ROW_HEIGHT = 25;
const fullTitles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven'];
for (let i = 0; i < 30; i++) {
fullTitles.push(`Mock${i}`);
}

beforeEach(() => {
// create a console.log spy for later use
cy.window().then((win) => {
cy.spy(win.console, "log");
});
});

it('should display Example title', () => {
cy.visit(`${Cypress.config('baseExampleUrl')}/example-csp-header.html`);
cy.get('h2').contains('Demonstrates');
cy.get('h2 + ul > li').first().contains('column span');
});

it('should have exact column titles', () => {
cy.get('#myGrid')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(fullTitles[index]));
});

it('should expect 1st row to be 1 column spanned to the entire width', () => {
cy.get(`[style="top:${GRID_ROW_HEIGHT * 0}px"] > .slick-cell:nth(0)`).should('contain', 'Task 0');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 0}px"] > .slick-cell:nth(1)`).should('not.exist');
});

it('should expect 2nd row to be 4 columns and not be spanned', () => {
cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0)`).should('contain', 'Task 1');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(1)`).should('contain', '5 days');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(2)`).should('contain', '01/05/2009');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(3)`).contains(/(true|false)/);
});

it('should expect 3rd row to be 1 column spanned to the entire width', () => {
cy.get(`[style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(0)`).should('contain', 'Task 2');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(1)`).should('not.exist');
});

it('should expect 4th row to be 4 columns and not be spanned', () => {
cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0)`).should('contain', 'Task 3');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(1)`).should('contain', '5 days');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(2)`).should('contain', '01/05/2009');
cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(3)`).contains(/(true|false)/);
});
});
44 changes: 44 additions & 0 deletions examples/example-csp-header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html>
<head>
<link rel="shortcut icon" type="image/ico" href="favicon.ico" />
<link rel="stylesheet" href="../slick.grid.css" type="text/css"/>
<link rel="stylesheet" href="examples.css" type="text/css"/>
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self' https://cdn.jsdelivr.net 'nonce-random-string' 'nonce-browser-sync'; require-trusted-types-for 'script'; trusted-types dompurify"> -->
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types dompurify">
</head>
<body>
<table width="100%">
<tr>
<td valign="top" width="50%">
<div id="myGrid"></div>
</td>
<td valign="top">
<h2>
<a href="/examples/index.html" id="link">&#x2302;</a>
Demonstrates:
</h2>
<ul>
<li>column span</li>
</ul>
<h2>View Source:</h2>
<ul>
<li><A href="https://github.com/6pac/SlickGrid/blob/master/examples/example-colspan.html" target="_sourcewindow"> View the source for this example on Github</a></li>
</ul>
</td>
</tr>
</table>

<script src="https://cdn.jsdelivr.net/npm/sortablejs/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
<!-- <script src="./example-csp-policy.js"></script> -->

<script src="../slick.core.js"></script>
<script src="../slick.interactions.js"></script>
<script src="../slick.grid.js"></script>
<script src="../plugins/slick.cellrangedecorator.js"></script>
<script src="../plugins/slick.cellrangeselector.js"></script>
<script src="../plugins/slick.cellselectionmodel.js"></script>
<script src="./example-csp-header.js"></script>
</body>
</html>
61 changes: 61 additions & 0 deletions examples/example-csp-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
document.addEventListener("DOMContentLoaded", function () {
var grid;
var columns = [
{ id: "title", name: "Title", field: "title" },
{ id: "duration", name: "Duration", field: "duration" },
{ id: "%", name: "% Complete", field: "percentComplete", selectable: false, width: 100 },
{ id: "start", name: "Start", field: "start" },
{ id: "finish", name: "Finish", field: "finish" },
{ id: "effort-driven", name: "Effort Driven", field: "effortDriven", width: 100 }
];

var options = {
enableCellNavigation: true,
enableColumnReorder: false,
sanitizer: (html) => window.DOMPurify.sanitize(html, { RETURN_TRUSTED_TYPE: true })
};
let gridElement = document.getElementById("myGrid");
gridElement.style.width = "600px";
gridElement.style.height = "500px";

let linkElement = document.getElementById("link");
//text-decoration: none; font-size: 22px
linkElement.style.textDecoration = "none";
linkElement.style.fontSize = "22px";

var data = [];
for (var i = 0; i < 500; i++) {
data[i] = {
title: "Task " + i,
duration: "5 days",
percentComplete: Math.round(Math.random() * 100),
start: "01/01/2009",
finish: "01/05/2009",
effortDriven: (i % 5 === 0)
};
}

data.getItemMetadata = function (row) {
if (row % 2 === 1) {
return {
"columns": {
"duration": {
"colspan": 3
}
}
};
} else {
return {
"columns": {
0: {
"colspan": "*"
}
}
};
}
};

grid = new Slick.Grid("#myGrid", data, columns, options);

grid.setSelectionModel(new Slick.CellSelectionModel());
});
13 changes: 9 additions & 4 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,15 @@ <h2>Grouping</h2>
<h2>Other Features</h2>
<div>
<ul>
<li><a href="example-explicit-initialization.html">Explicit initialization</a></li>
<li><a href="example9-row-reordering.html">Row selection &amp; reordering</a></li>
<li><a href="example10-async-post-render.html">Using background post-rendering to add graphs</a></li>
<li><a href="example10-async-post-render-cleanup.html">Background post-rendering with async cleanup</a></li>
<li><a href="./example-explicit-initialization.html">Explicit initialization</a></li>
<li><a href="./example9-row-reordering.html">Row selection &amp; reordering</a></li>
<li><a href="./example9-row-reordering-simple.html">Row reordering (simple example without delete)</a></li>
<li><a href="./example10-async-post-render.html">Using background post-rendering to add graphs</a></li>
<li><a href="./example10a-async-post-render-cleanup.html">Background post-rendering with async cleanup</a></li>
<li><a href="./example-auto-scroll-when-dragging.html">Auto scroll when dragging</a></li>
<li><a href="./example-column-hidden.html">Grid with Hidden Columns</a></li>
<li><a href="./example-frozen-columns-and-column-group-hidden-col.html">Frozen Grid with Hidden Columns</a></li>
<li><a href="./example-csp-header.html">CSP Header (Content Security Policy)</a></li>
</ul>
</div>
<h2>Bootstrap, Dynamic Grids and Third Party component editors</h2>
Expand Down
15 changes: 10 additions & 5 deletions slick.grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,8 @@ if (typeof Slick === "undefined") {
const headerRowTarget = hasFrozenColumns() ? ((i <= options.frozenColumn) ? _headerRowL : _headerRowR) : _headerRowL;

const header = utils.createDomElement('div', { id: `${uid + m.id}`, dataset: { id: m.id }, className: 'ui-state-default slick-header-column', title: m.toolTip || '' }, headerTarget);
utils.createDomElement('span', { className: 'slick-column-name', innerHTML: sanitizeHtmlString(m.name) }, header);
const colNameElm = utils.createDomElement('span', { className: 'slick-column-name' }, header);
colNameElm.innerHTML = sanitizeHtmlString(m.name);
utils.width(header, m.width - headerColumnWidthDiff);

let classname = m.headerCssClass || null;
Expand Down Expand Up @@ -2743,7 +2744,8 @@ if (typeof Slick === "undefined") {
// headers have not yet been created, create a new node
let header = getHeader(columnDef);
headerColEl = utils.createDomElement('div', { id: dummyHeaderColElId, className: 'ui-state-default slick-header-column', }, header);
utils.createDomElement('span', { className: 'slick-column-name', innerHTML: sanitizeHtmlString(columnDef.name) }, headerColEl);
const colNameElm = utils.createDomElement('span', { className: 'slick-column-name' }, headerColEl);
colNameElm.innerHTML = sanitizeHtmlString(columnDef.name);
clone.style.cssText = 'position: absolute; visibility: hidden;right: auto;text-overflow: initial;white-space: nowrap;';
if (columnDef.headerCssClass) {
headerColEl.classList.add(...columnDef.headerCssClass.split(' '));
Expand Down Expand Up @@ -4125,7 +4127,8 @@ if (typeof Slick === "undefined") {
return;
}

var x = utils.createDomElement('div', { innerHTML: sanitizeHtmlString(stringArray.join('')) });
var x = document.createElement('div');
x.innerHTML = sanitizeHtmlString(stringArray.join(''));
var processedRow;
var node;
while ((processedRow = processedRows.pop()) != null) {
Expand Down Expand Up @@ -4185,8 +4188,10 @@ if (typeof Slick === "undefined") {

if (!rows.length) { return; }

let x = utils.createDomElement('div', { innerHTML: sanitizeHtmlString(stringArrayL.join('')) });
let xRight = utils.createDomElement('div', { innerHTML: sanitizeHtmlString(stringArrayR.join('')) });
const x = document.createElement('div');
const xRight = document.createElement('div');
x.innerHTML = sanitizeHtmlString(stringArrayL.join(''));
xRight.innerHTML = sanitizeHtmlString(stringArrayR.join(''));

for (var i = 0, ii = rows.length; i < ii; i++) {
if (( hasFrozenRows ) && ( rows[i] >= actualFrozenRow )) {
Expand Down

0 comments on commit e722456

Please sign in to comment.