Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/demo/getScrollBarSize.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ nav:
---

<code src="../examples/getScrollBarSize.tsx"></code>

When using a Content Security Policy that requires a style nonce, pass the CSP
config as the second argument to `getTargetScrollBarSize`. The nonce is applied
to the temporary styles used for measurement:

```ts
getTargetScrollBarSize(element, { nonce: 'your-nonce' });
```

Components using Ant Design's `ConfigProvider` should forward its `csp` config
to this argument. This utility does not read React context automatically.
12 changes: 9 additions & 3 deletions src/getScrollBarSize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { removeCSS, updateCSS } from './Dom/dynamicCSS';

type ScrollBarSize = { width: number; height: number };
type CSPConfig = { nonce?: string };

type ExtendCSSStyleDeclaration = CSSStyleDeclaration & {
scrollbarColor?: string;
Expand All @@ -10,7 +11,10 @@ type ExtendCSSStyleDeclaration = CSSStyleDeclaration & {

let cached: ScrollBarSize;

function measureScrollbarSize(ele?: HTMLElement): ScrollBarSize {
function measureScrollbarSize(
ele?: HTMLElement,
csp?: CSPConfig,
): ScrollBarSize {
const randomId = `rc-scrollbar-measure-${Math.random()
.toString(36)
.substring(7)}`;
Expand Down Expand Up @@ -53,6 +57,7 @@ ${widthStyle}
${heightStyle}
}`,
randomId,
{ csp },
);
} catch (e) {
// Can't wrap, just log error
Expand Down Expand Up @@ -98,7 +103,8 @@ export default function getScrollBarSize(fresh?: boolean): number {
return cached.width;
}

export function getTargetScrollBarSize(target: HTMLElement) {
/** Pass the application's CSP config when measuring a target's scrollbar. */
export function getTargetScrollBarSize(target: HTMLElement, csp?: CSPConfig) {
if (
typeof document === 'undefined' ||
!target ||
Expand All @@ -107,5 +113,5 @@ export function getTargetScrollBarSize(target: HTMLElement) {
return { width: 0, height: 0 };
}

return measureScrollbarSize(target);
return measureScrollbarSize(target, csp);
}
47 changes: 47 additions & 0 deletions tests/getScrollBarSize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import getScrollBarSize, {
getTargetScrollBarSize,
} from '../src/getScrollBarSize';
import { spyElementPrototypes } from '../src/test/domHook';
import { clearContainerCache } from '../src/Dom/dynamicCSS';

const DEFAULT_SIZE = 16;

Expand Down Expand Up @@ -39,6 +40,52 @@ describe('getScrollBarSize', () => {
});

describe('getTargetScrollBarSize', () => {
afterEach(() => {
jest.restoreAllMocks();
clearContainerCache();
});

it.each([undefined, {}, { nonce: 'test-nonce' }])(
'measures and cleans up with CSP config %p',
csp => {
const target = document.createElement('div');
jest.spyOn(window, 'getComputedStyle').mockImplementation(
(_, pseudoElement) =>
({
width: pseudoElement ? '12px' : '100px',
height: pseudoElement ? '8px' : '100px',
scrollbarColor: 'auto',
scrollbarWidth: 'auto',
}) as CSSStyleDeclaration,
);

const insertedStyles: HTMLStyleElement[] = [];
const appendChild = document.head.appendChild;
jest.spyOn(document.head, 'appendChild').mockImplementation(node => {
if (node instanceof HTMLStyleElement) {
// CSP checks the nonce at insertion, including the placeholder style.
expect(node.nonce).toBe(csp?.nonce || '');
insertedStyles.push(node);
}
return appendChild.call(document.head, node) as typeof node;
});

expect(getTargetScrollBarSize(target, csp)).toEqual({
width: DEFAULT_SIZE,
height: 0,
});
expect(insertedStyles).toHaveLength(2);
expect(insertedStyles[1].innerHTML).toContain('width: 12px;');
expect(insertedStyles[1].innerHTML).toContain('height: 8px;');
insertedStyles.forEach(style => {
expect(style.parentNode).toBeNull();
});
expect(
document.querySelector('[id^="rc-scrollbar-measure-"]'),
).toBeNull();
},
);

it('invalidate', () => {
expect(
getTargetScrollBarSize({ notValidateObject: true } as any),
Expand Down