@cdmx/wappler_ag_grid 2.1.6 โ 2.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/app_connect/components.hjson +1 -1
- package/dmx-ag-grid.js +23 -2
- package/package.json +1 -1
- package/tests/01-basic-render.html +1 -1
- package/tests/02-themes-locale.html +1 -1
- package/tests/03-editing.html +1 -1
- package/tests/04-selection.html +63 -1
- package/tests/05-action-buttons.html +1 -1
- package/tests/06-export.html +1 -1
- package/tests/07-import.html +1 -1
- package/tests/08-formatting.html +1 -1
- package/tests/09-grouping.html +1 -1
- package/tests/10-state-persistence.html +1 -1
- package/tests/11-layout.html +1 -1
- package/tests/12-methods.html +1 -1
- package/tests/13-flags-styles.html +5 -1
- package/tests/14-compact-view.html +1 -1
- package/tests/15-csp.html +98 -0
- package/tests/FINDINGS.md +229 -0
- package/tests/README.md +6 -0
- package/tests/VERSIONS.json +29 -0
- package/tests/csp-hashes.cjs +191 -0
- package/tests/data/sample-data.js +12 -1
- package/tests/findings.json +892 -0
- package/tests/lib/common-head.html +1 -1
- package/tests/lib/version-banner.js +46 -0
- package/tests/run-all-puppeteer.cjs +3 -2
- package/tests/run-all.cjs +3 -2
package/README.md
CHANGED
|
@@ -2,6 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
**Major Update:** This release upgrades AG Grid to v35.1.0, bringing continued performance improvements and new features.
|
|
4
4
|
|
|
5
|
+
## ๐ What's New in v2.1.8
|
|
6
|
+
|
|
7
|
+
**`style-src` without `'unsafe-inline'`, via a page nonce.** AG Grid v35 injects its theme CSS as
|
|
8
|
+
`<style>` elements at runtime, so any policy without `'unsafe-inline'` broke the grid. This release:
|
|
9
|
+
|
|
10
|
+
- ships `ag-grid-community.min.noStyle.js` under the existing `js/ag-grid-community.min.js` name.
|
|
11
|
+
The with-styles build injected 14 `<style>` blocks at script load that cannot take a nonce, and
|
|
12
|
+
AG Grid deleted them again the moment a grid initialised (they only serve legacy-theme mode, which
|
|
13
|
+
this module never uses). Nothing visible changes and existing `<script>` links keep working โ
|
|
14
|
+
update the module in each project and the file is overwritten.
|
|
15
|
+
- reads a page nonce and passes it to AG Grid (`styleNonce`) and to the module's own three
|
|
16
|
+
`<style>` blocks. The nonce is page-global, as AG Grid requires; it is taken from
|
|
17
|
+
`<meta name="csp-nonce" content="โฆ">` or from any `<script nonce="โฆ">` already on the page. Pages
|
|
18
|
+
without a nonce behave exactly as before.
|
|
19
|
+
|
|
20
|
+
To use it, generate a fresh random nonce **per response** (a fixed value is equivalent to
|
|
21
|
+
`'unsafe-inline'`) and put it in both places:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<meta name="csp-nonce" content="<nonce>"> <!-- layout <head> -->
|
|
25
|
+
```
|
|
26
|
+
```
|
|
27
|
+
Content-Security-Policy: style-src 'self' 'nonce-<nonce>'; img-src 'self' data:; font-src 'self' data:
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`img-src data:` covers AG Grid's SVG icons, `font-src data:` the icon fonts embedded in the theme
|
|
31
|
+
CSS; `script-src` needs nothing from the module (`'unsafe-eval'` only with `enable_cell_expressions`).
|
|
32
|
+
|
|
33
|
+
Server side:
|
|
34
|
+
|
|
35
|
+
- **Express / Wappler NodeJS** โ middleware registered before the page routes (in Wappler, a custom
|
|
36
|
+
routes file under `extensions/server_connect/routes/` receives the Express `app`):
|
|
37
|
+
```js
|
|
38
|
+
const crypto = require('crypto');
|
|
39
|
+
app.use((req, res, next) => {
|
|
40
|
+
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
|
|
41
|
+
res.setHeader('Content-Security-Policy',
|
|
42
|
+
`style-src 'self' 'nonce-${res.locals.cspNonce}'; img-src 'self' data:; font-src 'self' data:`);
|
|
43
|
+
next();
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
and `<meta name="csp-nonce" content="<%= cspNonce %>">` in the EJS layout.
|
|
47
|
+
- **nginx** โ `set $csp_nonce $request_id;`, `add_header Content-Security-Policy "style-src 'self'
|
|
48
|
+
'nonce-$csp_nonce'; img-src 'self' data:; font-src 'self' data:" always;`, and inject it into the
|
|
49
|
+
HTML with `sub_filter '__CSP_NONCE__' $csp_nonce; sub_filter_once off;` against
|
|
50
|
+
`<meta name="csp-nonce" content="__CSP_NONCE__">` in the layout (`sub_filter` needs uncompressed
|
|
51
|
+
upstream HTML: `proxy_set_header Accept-Encoding "";`).
|
|
52
|
+
- **PHP** โ `$nonce = base64_encode(random_bytes(16));` then `header("Content-Security-Policy: โฆ
|
|
53
|
+
'nonce-$nonce' โฆ");` and `<meta name="csp-nonce" content="<?= $nonce ?>">`.
|
|
54
|
+
|
|
55
|
+
Regression coverage: `tests/15-csp.html` is served under `style-src 'self' 'nonce-csp15'` with no
|
|
56
|
+
`'unsafe-inline'` and asserts zero violations, every `<style>` nonced, and the layout,
|
|
57
|
+
`vert_center_cell_data` and `fixed_horizontal_scroll` styles applied.
|
|
58
|
+
|
|
59
|
+
## ๐ What's New in v2.1.7
|
|
60
|
+
|
|
61
|
+
**Content Security Policy compliance.** The status toggle column (rendered when a grid has a
|
|
62
|
+
`status` field and `row_status_event` is enabled) previously emitted an inline
|
|
63
|
+
`onclick="handleStatusToggle(...)"` attribute per row. Inline event handlers are blocked by any
|
|
64
|
+
CSP without `'unsafe-inline'` in `script-src`, and they cannot be allowed by hash because the
|
|
65
|
+
attribute interpolates per-row data โ so under a locked-down policy the toggle rendered normally
|
|
66
|
+
but silently stopped firing `row_status_enabled` / `row_status_disabled`.
|
|
67
|
+
|
|
68
|
+
The renderer now emits `data-status-col` / `data-status-value` / `data-status-id` attributes, and
|
|
69
|
+
a single delegated `click` listener on the grid container dispatches the events. Behaviour is
|
|
70
|
+
unchanged; no configuration or view changes are required.
|
|
71
|
+
|
|
72
|
+
- **No inline event handlers** on any documented rendering path, so `script-src` needs neither
|
|
73
|
+
`'unsafe-inline'` nor per-row hashes. The one exception is the legacy `clickCellRenderer`, which
|
|
74
|
+
is only reachable by passing raw AG Grid column definitions through `column_defs` with
|
|
75
|
+
`cellRenderer: 'clickCellRenderer'`; it still emits `onclick=`/`style=` and cannot be used under
|
|
76
|
+
a strict policy.
|
|
77
|
+
- Toggle values are now attribute-escaped rather than interpolated into a JavaScript string
|
|
78
|
+
literal, closing a quote-breakout in the previous renderer.
|
|
79
|
+
|
|
80
|
+
Regression coverage lives in `tests/04-selection.html` (`window.__statusCheck`), which asserts the
|
|
81
|
+
grid emits zero `[onclick]` attributes and that the toggle still dispatches its events.
|
|
82
|
+
|
|
83
|
+
### Alternative without a nonce: hash allow-list
|
|
84
|
+
|
|
85
|
+
If a per-request nonce is not possible on your stack, every `<style>` block AG Grid and this module
|
|
86
|
+
inject can be allowed by `'sha256-โฆ'` hash instead. `node tests/csp-hashes.cjs` harvests them from
|
|
87
|
+
the test scenarios and prints the `style-src` directive (helmet array form too); `--verify` reloads
|
|
88
|
+
every scenario under that header and exits 1 on any violation. Caveats, which are why the nonce is
|
|
89
|
+
the recommended route: the hashes are bound to the exact bytes of `ag-grid-community.min.js` and
|
|
90
|
+
`dmx-ag-grid.js` (regenerate after **any** upgrade of either), AG Grid injects component CSS lazily
|
|
91
|
+
so a feature the scenarios don't exercise can need one more hash (that component renders unstyled and
|
|
92
|
+
the console names the hash), and hashes cover `<style>` elements only โ inline `style="โฆ"` attributes
|
|
93
|
+
in your own pages still need `style-src-attr` handling.
|
|
94
|
+
|
|
5
95
|
## ๐ What's New in v2.1.1
|
|
6
96
|
|
|
7
97
|
1. **๐ฅ AG Grid v35.1.0** - Latest version with all community features
|
|
@@ -3862,7 +3862,7 @@
|
|
|
3862
3862
|
"allowed_children": {},
|
|
3863
3863
|
"copyFiles": [
|
|
3864
3864
|
{
|
|
3865
|
-
"src": "../../../node_modules/ag-grid-community/dist/ag-grid-community.min.js",
|
|
3865
|
+
"src": "../../../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js",
|
|
3866
3866
|
"dst": "js/ag-grid-community.min.js"
|
|
3867
3867
|
},
|
|
3868
3868
|
{
|
package/dmx-ag-grid.js
CHANGED
|
@@ -621,6 +621,10 @@ dmx.Component('ag-grid', {
|
|
|
621
621
|
|
|
622
622
|
const gridThemeClass = options.dark_mode ? `${options.grid_theme}-dark` : options.grid_theme;
|
|
623
623
|
this.$node.innerHTML = `<div id=${options.id}-grid class="${gridThemeClass}"></div>`;
|
|
624
|
+
// CSP: nonce for every <style> AG Grid and this module inject. Page-global, read from
|
|
625
|
+
// <meta name="csp-nonce" content="โฆ"> or any nonce'd <script>; undefined when the page has none.
|
|
626
|
+
const cspNonce = (document.querySelector('meta[name="csp-nonce"]') || {}).content
|
|
627
|
+
|| (document.querySelector('script[nonce]') || {}).nonce || undefined;
|
|
624
628
|
|
|
625
629
|
// Apply theme mode using data attribute
|
|
626
630
|
const themeContainer = this.$node.querySelector(`#${options.id}-grid`);
|
|
@@ -648,6 +652,18 @@ dmx.Component('ag-grid', {
|
|
|
648
652
|
this.dispatchEvent('row_status_disabled')
|
|
649
653
|
}
|
|
650
654
|
};
|
|
655
|
+
// ponytail: delegated โ checkboxCellRenderer emits no inline onclick (CSP script-src).
|
|
656
|
+
// Delegation rather than per-input binding because AG Grid virtualizes rows.
|
|
657
|
+
if (themeContainer) {
|
|
658
|
+
themeContainer.removeEventListener('click', this._statusToggleHandler);
|
|
659
|
+
this._statusToggleHandler = (event) => {
|
|
660
|
+
const input = event.target.closest('.switch-input[data-status-id]');
|
|
661
|
+
if (input) {
|
|
662
|
+
window.handleStatusToggle(event, input.dataset.statusCol, input.dataset.statusValue, input.dataset.statusId);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
themeContainer.addEventListener('click', this._statusToggleHandler);
|
|
666
|
+
}
|
|
651
667
|
function clickCellRenderer(params) {
|
|
652
668
|
const idValue = params.data.id;
|
|
653
669
|
const columnName = params.colDef.field;
|
|
@@ -696,13 +712,14 @@ dmx.Component('ag-grid', {
|
|
|
696
712
|
}
|
|
697
713
|
// Assuming `value` is a boolean representing the status
|
|
698
714
|
const checked = value==true ? "checked" : "";
|
|
715
|
+
const attr = (v) => String(v).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
699
716
|
return `
|
|
700
717
|
<div class="col pt-2 pb-1 ps-1 pe-1 d-flex justify-content-center">
|
|
701
718
|
<label class="switch switch-success">
|
|
702
|
-
<input
|
|
719
|
+
<input
|
|
703
720
|
type="checkbox" class="switch-input"
|
|
704
721
|
${checked}
|
|
705
|
-
|
|
722
|
+
data-status-col="${attr(columnName)}" data-status-value="${attr(value)}" data-status-id="${attr(idValue)}"
|
|
706
723
|
/>
|
|
707
724
|
<span class="switch-toggle-slider" role="status">
|
|
708
725
|
</span>
|
|
@@ -1810,6 +1827,7 @@ dmx.Component('ag-grid', {
|
|
|
1810
1827
|
localeText = AG_GRID_LOCALE_ES
|
|
1811
1828
|
}
|
|
1812
1829
|
const gridOptions = {
|
|
1830
|
+
styleNonce: cspNonce,
|
|
1813
1831
|
...(idFieldPresent ? { getRowId: params => String(params.data.id) } : {}),
|
|
1814
1832
|
columnDefs: (groupedColumnDefs && groupedColumnDefs.length > 0) ? groupedColumnDefs : columnDefs,
|
|
1815
1833
|
getRowStyle: options.rstyles ? createRowStyleFunction(options.rstyles): undefined,
|
|
@@ -2140,6 +2158,7 @@ dmx.Component('ag-grid', {
|
|
|
2140
2158
|
const gridElement = document.getElementById(options.id+'-grid');
|
|
2141
2159
|
if (options.vert_center_cell_data) {
|
|
2142
2160
|
const styleElement = document.createElement('style');
|
|
2161
|
+
if (cspNonce) styleElement.setAttribute('nonce', cspNonce);
|
|
2143
2162
|
if (options.vert_center_cell_data) {
|
|
2144
2163
|
styleElement.textContent += `
|
|
2145
2164
|
.ag-cell {
|
|
@@ -2237,6 +2256,7 @@ dmx.Component('ag-grid', {
|
|
|
2237
2256
|
if (options.fixed_horizontal_scroll) {
|
|
2238
2257
|
const styleElement = document.createElement('style');
|
|
2239
2258
|
styleElement.id = 'hovering-bar-style';
|
|
2259
|
+
if (cspNonce) styleElement.setAttribute('nonce', cspNonce);
|
|
2240
2260
|
const agRootWrapper = gridElement.querySelector('.ag-root-wrapper');
|
|
2241
2261
|
const bodyHorizontalScrollElement = gridElement.querySelector('.ag-body-horizontal-scroll');
|
|
2242
2262
|
const rootWrapperWidth = agRootWrapper.clientWidth;
|
|
@@ -2948,6 +2968,7 @@ dmx.Component('ag-grid', {
|
|
|
2948
2968
|
`;
|
|
2949
2969
|
|
|
2950
2970
|
const paginationPanelStyle = document.createElement('style');
|
|
2971
|
+
if (cspNonce) paginationPanelStyle.setAttribute('nonce', cspNonce);
|
|
2951
2972
|
paginationPanelStyle.innerHTML = paginationPanelCss;
|
|
2952
2973
|
document.head.appendChild(paginationPanelStyle);
|
|
2953
2974
|
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<script src="./lib/version-banner.js" defer></script>
|
|
15
15
|
<script src="./lib/test-helpers.js" defer></script>
|
|
16
16
|
<script src="./lib/page-template.js" defer></script>
|
|
17
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
17
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
18
18
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
19
19
|
<script src="./data/sample-data.js" defer></script>
|
|
20
20
|
</head>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<script src="./lib/version-banner.js" defer></script>
|
|
18
18
|
<script src="./lib/test-helpers.js" defer></script>
|
|
19
19
|
<script src="./lib/page-template.js" defer></script>
|
|
20
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
20
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
21
21
|
<script src="../node_modules/@ag-grid-community/locale/dist/umd/@ag-grid-community/locale.min.js" defer></script>
|
|
22
22
|
<script src="../ru.js" defer></script>
|
|
23
23
|
<script src="../dmx-ag-grid.js" defer></script>
|
package/tests/03-editing.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script src="./lib/version-banner.js" defer></script>
|
|
13
13
|
<script src="./lib/test-helpers.js" defer></script>
|
|
14
14
|
<script src="./lib/page-template.js" defer></script>
|
|
15
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
15
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
16
16
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
17
17
|
<script src="./data/sample-data.js" defer></script>
|
|
18
18
|
</head>
|
package/tests/04-selection.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
<main class="test-main">
|
|
30
30
|
<dmx-test-data id="rows" source="employees"></dmx-test-data>
|
|
31
|
+
<dmx-test-data id="statusData" source="statusRows"></dmx-test-data>
|
|
31
32
|
<dmx-event-log id="evLog"></dmx-event-log>
|
|
32
33
|
|
|
33
34
|
<section class="feature">
|
|
@@ -69,6 +70,29 @@
|
|
|
69
70
|
</div>
|
|
70
71
|
</section>
|
|
71
72
|
|
|
73
|
+
<section class="feature">
|
|
74
|
+
<h2>Status toggle column</h2>
|
|
75
|
+
<p class="desc">
|
|
76
|
+
Data has a <code>status</code> field, so <code>row_status_event=true</code> renders
|
|
77
|
+
<code>checkboxCellRenderer</code>. The toggle must dispatch its event via a delegated
|
|
78
|
+
listener and emit <strong>no inline <code>onclick</code></strong> โ inline handlers are
|
|
79
|
+
blocked by a CSP without <code>'unsafe-inline'</code> in <code>script-src</code>.
|
|
80
|
+
The puppeteer runner reads <code>window.__statusCheck</code>.
|
|
81
|
+
</p>
|
|
82
|
+
<div class="grid-wrap">
|
|
83
|
+
<dmx-ag-grid
|
|
84
|
+
id="statusGrid"
|
|
85
|
+
dmx-bind:data="statusData.value"
|
|
86
|
+
grid_theme="ag-theme-alpine"
|
|
87
|
+
dmx-bind:row_status_event="true"
|
|
88
|
+
dmx-bind:pagination="false"
|
|
89
|
+
dmx-on:row_status_enabled="evLog.add('row_status_enabled')"
|
|
90
|
+
dmx-on:row_status_disabled="evLog.add('row_status_disabled')">
|
|
91
|
+
</dmx-ag-grid>
|
|
92
|
+
</div>
|
|
93
|
+
<pre class="log" id="status-check"></pre>
|
|
94
|
+
</section>
|
|
95
|
+
|
|
72
96
|
<section class="feature">
|
|
73
97
|
<h2>Single-row selection</h2>
|
|
74
98
|
<p class="desc"><code>row_selection="singleRow"</code> โ clicking a row replaces selection.</p>
|
|
@@ -88,6 +112,44 @@
|
|
|
88
112
|
const c = TestKit.grid('selGrid');
|
|
89
113
|
console.log('selectedRows', c && c.get('selectedRows'));
|
|
90
114
|
}
|
|
115
|
+
// Status-toggle assertions โ exposed as window.__statusCheck for the runners.
|
|
116
|
+
// Guards the CSP fix: checkboxCellRenderer must emit data-status-* attributes and no
|
|
117
|
+
// inline onclick, with a delegated listener still dispatching row_status_*.
|
|
118
|
+
function runStatusChecks(tries) {
|
|
119
|
+
tries = tries || 0;
|
|
120
|
+
if (document.body.dataset.gridsReady !== 'true') return setTimeout(() => runStatusChecks(tries), 200);
|
|
121
|
+
const root = document.querySelector('#statusGrid-grid');
|
|
122
|
+
const input = root && root.querySelector('.switch-input');
|
|
123
|
+
if (!input) {
|
|
124
|
+
if (tries > 25) return publish({ toggle_rendered: { expect: true, actual: false } });
|
|
125
|
+
return setTimeout(() => runStatusChecks(tries + 1), 200);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const checks = {
|
|
129
|
+
// First row is status:true, so it renders checked โ clicking it disables.
|
|
130
|
+
toggle_rendered: { expect: true, actual: true },
|
|
131
|
+
no_inline_onclick: { expect: 0, actual: root.querySelectorAll('[onclick]').length },
|
|
132
|
+
toggle_has_dataset: { expect: true, actual: !!(input.dataset.statusId && input.dataset.statusCol) }
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const log = dmx.app.get('evLog');
|
|
136
|
+
input.click();
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
const last = log && log.last;
|
|
139
|
+
checks.toggle_dispatches = { expect: 'row_status_disabled', actual: (last && last.name) || null };
|
|
140
|
+
publish(checks);
|
|
141
|
+
}, 150);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function publish(checks) {
|
|
145
|
+
const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
|
|
146
|
+
window.__statusCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
|
|
147
|
+
document.getElementById('status-check').textContent = Object.entries(checks)
|
|
148
|
+
.map(([k, v]) => (v.actual === v.expect ? 'โ' : 'โ') + ' ' + k + ' โ expected ' + v.expect + ', got ' + v.actual)
|
|
149
|
+
.join('\n');
|
|
150
|
+
}
|
|
151
|
+
runStatusChecks();
|
|
152
|
+
|
|
91
153
|
(function pollLog() {
|
|
92
154
|
if (!window.dmx || !dmx.app) return setTimeout(pollLog, 100);
|
|
93
155
|
const log = dmx.app.get('evLog');
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script src="./lib/version-banner.js" defer></script>
|
|
13
13
|
<script src="./lib/test-helpers.js" defer></script>
|
|
14
14
|
<script src="./lib/page-template.js" defer></script>
|
|
15
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
15
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
16
16
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
17
17
|
<script src="./data/sample-data.js" defer></script>
|
|
18
18
|
</head>
|
package/tests/06-export.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../node_modules/pdfmake/build/pdfmake.min.js" defer></script>
|
|
16
16
|
<script src="../node_modules/pdfmake/build/vfs_fonts.js" defer></script>
|
|
17
17
|
<script src="../node_modules/exceljs/dist/exceljs.min.js" defer></script>
|
package/tests/07-import.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../node_modules/papaparse/papaparse.min.js" defer></script>
|
|
16
16
|
<script src="../node_modules/read-excel-file/bundle/read-excel-file.min.js" defer></script>
|
|
17
17
|
<script src="../dmx-ag-grid.js" defer></script>
|
package/tests/08-formatting.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
package/tests/09-grouping.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
package/tests/11-layout.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
<style>
|
package/tests/12-methods.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
13
|
<script src="./lib/page-template.js" defer></script>
|
|
14
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
|
|
@@ -311,6 +311,10 @@
|
|
|
311
311
|
if (!employees.length) return setTimeout(runChecks, 200);
|
|
312
312
|
// Wait an extra cycle so the legacy-form re-render lands before we count rows.
|
|
313
313
|
if (!document.querySelector('#rsLegacy-grid .ag-row [col-id]')) return setTimeout(runChecks, 200);
|
|
314
|
+
// cellStyle lands a tick after the rows: wait for a styled salary cell (capped so a real
|
|
315
|
+
// regression still surfaces as a failed count rather than a page that never publishes).
|
|
316
|
+
runChecks.tries = (runChecks.tries || 0) + 1;
|
|
317
|
+
if (runChecks.tries < 25 && !document.querySelector('#csCell-grid [col-id="salary"][style*="color"]')) return setTimeout(runChecks, 200);
|
|
314
318
|
|
|
315
319
|
// Expected hand-counts derived from sample-data.js
|
|
316
320
|
const vipRows = employees.filter(r => r.rating >= 4.7 && r.active === true).length;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script src="./lib/version-banner.js" defer></script>
|
|
13
13
|
<script src="./lib/test-helpers.js" defer></script>
|
|
14
14
|
<script src="./lib/page-template.js" defer></script>
|
|
15
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
15
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
16
16
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
17
17
|
<script src="./data/sample-data.js" defer></script>
|
|
18
18
|
</head>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<!-- Strict policy: no 'unsafe-inline' anywhere. Every <style> AG Grid or the module injects must carry the nonce. -->
|
|
6
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'nonce-csp15'; style-src 'self' 'nonce-csp15'; img-src 'self' data:; font-src 'self' data:">
|
|
7
|
+
<meta name="csp-nonce" content="csp15">
|
|
8
|
+
<title>15 ยท CSP nonce</title>
|
|
9
|
+
<script nonce="csp15">
|
|
10
|
+
// Must be installed before any deferred script runs so every violation is counted.
|
|
11
|
+
window.__cspViolations = [];
|
|
12
|
+
document.addEventListener('securitypolicyviolation', e => window.__cspViolations.push(e.violatedDirective + ' ' + (e.sourceFile || '').split('/').pop() + ':' + e.lineNumber));
|
|
13
|
+
</script>
|
|
14
|
+
<link rel="stylesheet" href="./lib/test-suite.css">
|
|
15
|
+
<link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-alpine.css">
|
|
16
|
+
<link rel="stylesheet" href="../switch-toggle-slider.css">
|
|
17
|
+
<script src="./dmxAppConnect/dmxAppConnect.js" defer></script>
|
|
18
|
+
<script src="./lib/version-banner.js" defer></script>
|
|
19
|
+
<script src="./lib/test-helpers.js" defer></script>
|
|
20
|
+
<script src="./lib/page-template.js" defer></script>
|
|
21
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
22
|
+
<script src="../dmx-ag-grid.js" defer></script>
|
|
23
|
+
<script src="./data/sample-data.js" defer></script>
|
|
24
|
+
</head>
|
|
25
|
+
<body is="dmx-app" id="page15">
|
|
26
|
+
|
|
27
|
+
<script nonce="csp15">
|
|
28
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
29
|
+
TestPage.header({
|
|
30
|
+
title: '15 ยท CSP nonce',
|
|
31
|
+
summary: "Page served under style-src 'self' 'nonce-csp15' with no 'unsafe-inline'. The module reads the nonce from <meta name=\"csp-nonce\"> and passes it to AG Grid (styleNonce) and its own <style> blocks."
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<main class="test-main">
|
|
37
|
+
<dmx-test-data id="rows" source="employees"></dmx-test-data>
|
|
38
|
+
<dmx-test-data id="statusData" source="statusRows"></dmx-test-data>
|
|
39
|
+
|
|
40
|
+
<section class="feature">
|
|
41
|
+
<h2>Grid under a nonce-only style-src</h2>
|
|
42
|
+
<p class="desc">
|
|
43
|
+
Pagination, <code>vert_center_cell_data</code>, <code>fixed_horizontal_scroll</code> and the status toggle โ
|
|
44
|
+
every path where the module itself creates a <code><style></code>. The puppeteer runner reads
|
|
45
|
+
<code>window.__cspCheck</code>.
|
|
46
|
+
</p>
|
|
47
|
+
<div class="grid-wrap">
|
|
48
|
+
<dmx-ag-grid
|
|
49
|
+
id="cspGrid"
|
|
50
|
+
dmx-bind:data="rows.value"
|
|
51
|
+
grid_theme="ag-theme-alpine"
|
|
52
|
+
dmx-bind:pagination="true"
|
|
53
|
+
dmx-bind:pagination_page_size="10"
|
|
54
|
+
dmx-bind:vert_center_cell_data="true"
|
|
55
|
+
dmx-bind:fixed_horizontal_scroll="true">
|
|
56
|
+
</dmx-ag-grid>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="grid-wrap">
|
|
59
|
+
<dmx-ag-grid id="statusGrid" dmx-bind:data="statusData.value" grid_theme="ag-theme-alpine" dmx-bind:row_status_event="true" dmx-bind:pagination="false"></dmx-ag-grid>
|
|
60
|
+
</div>
|
|
61
|
+
<pre class="log" id="csp-check"></pre>
|
|
62
|
+
</section>
|
|
63
|
+
</main>
|
|
64
|
+
|
|
65
|
+
<script nonce="csp15">
|
|
66
|
+
function runCspChecks(tries) {
|
|
67
|
+
tries = tries || 0;
|
|
68
|
+
if (document.body.dataset.gridsReady !== 'true') {
|
|
69
|
+
if (tries > 50) return publish({ grids_ready: { expect: true, actual: false } });
|
|
70
|
+
return setTimeout(() => runCspChecks(tries + 1), 200);
|
|
71
|
+
}
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
const root = document.querySelector('#cspGrid-grid');
|
|
74
|
+
const wrap = root && root.querySelector('.ag-root-wrapper');
|
|
75
|
+
const cell = root && root.querySelector('.ag-cell');
|
|
76
|
+
const styles = [...document.querySelectorAll('style')];
|
|
77
|
+
publish({
|
|
78
|
+
csp_violations: { expect: 0, actual: window.__cspViolations.length },
|
|
79
|
+
style_elements_seen: { expect: true, actual: styles.length > 0 },
|
|
80
|
+
all_styles_nonced: { expect: 0, actual: styles.filter(s => s.nonce !== 'csp15').length },
|
|
81
|
+
layout_intact: { expect: 'flex', actual: wrap ? getComputedStyle(wrap).display : 'no-wrapper' },
|
|
82
|
+
vcenter_applied: { expect: 'flex', actual: cell ? getComputedStyle(cell).display : 'no-cell' },
|
|
83
|
+
hscroll_fixed: { expect: 'fixed', actual: (el => el ? getComputedStyle(el).position : 'none')(root && root.querySelector('.ag-body-horizontal-scroll')) }
|
|
84
|
+
});
|
|
85
|
+
if (window.__cspViolations.length) console.warn('CSP violations:', window.__cspViolations);
|
|
86
|
+
}, 400);
|
|
87
|
+
}
|
|
88
|
+
function publish(checks) {
|
|
89
|
+
const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
|
|
90
|
+
window.__cspCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
|
|
91
|
+
document.getElementById('csp-check').textContent = Object.entries(checks)
|
|
92
|
+
.map(([k, v]) => (v.actual === v.expect ? 'โ' : 'โ') + ' ' + k + ' โ expected ' + v.expect + ', got ' + v.actual)
|
|
93
|
+
.join('\n');
|
|
94
|
+
}
|
|
95
|
+
runCspChecks();
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|