Skip to content

Commit 4242393

Browse files
author
Steven Orvell
committed
Adds unsafeCss for composing values into css
Fixes #451 and #471. Non-literal values can now be included in styling with `css` by using the `unsafeCss` function. This is named "unsafe" so users know this they must use this carefully to avoid security issues.
1 parent e600612 commit 4242393

File tree

3 files changed

+46
-3
lines changed

3 files changed

+46
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1515
<!-- ### Fixed -->
1616
## Unreleased
1717

18+
### Added
19+
* Adds `unsafeCss` for composing "unsafe" values into `css`. Note, `CSSResult` is no longer constructable. ([#451](https://github.com/Polymer/lit-element/issues/451) and [#471](https://github.com/Polymer/lit-element/issues/471)).
20+
1821
### Fixed
1922
* Fixed a bug where we broke compatibility with closure compiler's property renaming optimizations. JSCompiler_renameProperty can't be a module export ([#465](https://github.com/Polymer/lit-element/pull/465)).
2023
* Fixed an issue with inheriting from `styles` property when extending a superclass that is never instanced. ([#470](https://github.com/Polymer/lit-element/pull/470)).

src/lib/css-tag.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,20 @@ found at http://polymer.github.io/PATENTS.txt
1212
export const supportsAdoptingStyleSheets =
1313
('adoptedStyleSheets' in Document.prototype);
1414

15+
const constructionToken = Symbol();
16+
1517
export class CSSResult {
1618

1719
_styleSheet?: CSSStyleSheet|null;
1820

1921
readonly cssText: string;
2022

21-
constructor(cssText: string) { this.cssText = cssText; }
23+
constructor(cssText: string, safeToken: symbol) {
24+
if (safeToken !== constructionToken) {
25+
throw new Error('CSSResult is not constructable. Use `unsafeCss` or `css` instead.');
26+
}
27+
this.cssText = cssText;
28+
}
2229

2330
// Note, this is a getter so that it's lazy. In practice, this means
2431
// stylesheets are not created until the first element instance is made.
@@ -37,6 +44,10 @@ export class CSSResult {
3744
}
3845
}
3946

47+
export const unsafeCss = (value: any) => {
48+
return new CSSResult(String(value), constructionToken);
49+
};
50+
4051
const textFromCSSResult = (value: CSSResult) => {
4152
if (value instanceof CSSResult) {
4253
return value.cssText;
@@ -48,9 +59,9 @@ const textFromCSSResult = (value: CSSResult) => {
4859
};
4960

5061
export const css =
51-
(strings: TemplateStringsArray, ...values: CSSResult[]): CSSResult => {
62+
(strings: TemplateStringsArray, ...values: CSSResult[]) => {
5263
const cssText = values.reduce(
5364
(acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1],
5465
strings[0]);
55-
return new CSSResult(cssText);
66+
return new CSSResult(cssText, constructionToken);
5667
};

src/test/lit-element_styling_test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import '@webcomponents/shadycss/apply-shim.min.js';
1616

1717
import {
1818
css,
19+
unsafeCss,
20+
CSSResult,
1921
html as htmlWithStyles,
2022
LitElement,
2123
} from '../lit-element.js';
@@ -432,6 +434,33 @@ suite('Static get styles', () => {
432434
assert.throws(() => { css`div { border: ${`2px solid blue;` as any}}`; });
433435
});
434436

437+
test('`CSSResult` cannot be constructed', async () => {
438+
// Note, this is done for security, instead use `css` or `unsafeCss`
439+
assert.throws(() => { new CSSResult('throw', Symbol()); });
440+
});
441+
442+
test('Any value can be used in `css` when included with `unsafeCss`', async () => {
443+
const name = generateElementName();
444+
const someVar = `2px solid blue`;
445+
customElements.define(name, class extends LitElement {
446+
static get styles() {
447+
return css`div {
448+
border: ${unsafeCss(someVar)};
449+
}`;
450+
}
451+
452+
render() {
453+
return htmlWithStyles`
454+
<div>Testing</div>`;
455+
}
456+
});
457+
const el = document.createElement(name);
458+
container.appendChild(el);
459+
await (el as LitElement).updateComplete;
460+
const div = el.shadowRoot!.querySelector('div');
461+
assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px');
462+
});
463+
435464
test('styles in render compose with `static get styles`', async () => {
436465
const name = generateElementName();
437466
customElements.define(name, class extends LitElement {

0 commit comments

Comments
 (0)