142 lines
5.0 KiB
JavaScript
142 lines
5.0 KiB
JavaScript
|
/**
|
||
|
* @license
|
||
|
* Copyright 2020 Google LLC
|
||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||
|
*/
|
||
|
// Any new exports need to be added to the export statement in
|
||
|
// `packages/lit/src/index.all.ts`.
|
||
|
import { html as coreHtml, svg as coreSvg } from './lit-html.js';
|
||
|
/**
|
||
|
* Prevents JSON injection attacks.
|
||
|
*
|
||
|
* The goals of this brand:
|
||
|
* 1) fast to check
|
||
|
* 2) code is small on the wire
|
||
|
* 3) multiple versions of Lit in a single page will all produce mutually
|
||
|
* interoperable StaticValues
|
||
|
* 4) normal JSON.parse (without an unusual reviver) can not produce a
|
||
|
* StaticValue
|
||
|
*
|
||
|
* Symbols satisfy (1), (2), and (4). We use Symbol.for to satisfy (3), but
|
||
|
* we don't care about the key, so we break ties via (2) and use the empty
|
||
|
* string.
|
||
|
*/
|
||
|
const brand = Symbol.for('');
|
||
|
/** Safely extracts the string part of a StaticValue. */
|
||
|
const unwrapStaticValue = (value) => {
|
||
|
if (value?.r !== brand) {
|
||
|
return undefined;
|
||
|
}
|
||
|
return value?.['_$litStatic$'];
|
||
|
};
|
||
|
/**
|
||
|
* Wraps a string so that it behaves like part of the static template
|
||
|
* strings instead of a dynamic value.
|
||
|
*
|
||
|
* Users must take care to ensure that adding the static string to the template
|
||
|
* results in well-formed HTML, or else templates may break unexpectedly.
|
||
|
*
|
||
|
* Note that this function is unsafe to use on untrusted content, as it will be
|
||
|
* directly parsed into HTML. Do not pass user input to this function
|
||
|
* without sanitizing it.
|
||
|
*
|
||
|
* Static values can be changed, but they will cause a complete re-render
|
||
|
* since they effectively create a new template.
|
||
|
*/
|
||
|
export const unsafeStatic = (value) => ({
|
||
|
['_$litStatic$']: value,
|
||
|
r: brand,
|
||
|
});
|
||
|
const textFromStatic = (value) => {
|
||
|
if (value['_$litStatic$'] !== undefined) {
|
||
|
return value['_$litStatic$'];
|
||
|
}
|
||
|
else {
|
||
|
throw new Error(`Value passed to 'literal' function must be a 'literal' result: ${value}. Use 'unsafeStatic' to pass non-literal values, but
|
||
|
take care to ensure page security.`);
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* Tags a string literal so that it behaves like part of the static template
|
||
|
* strings instead of a dynamic value.
|
||
|
*
|
||
|
* The only values that may be used in template expressions are other tagged
|
||
|
* `literal` results or `unsafeStatic` values (note that untrusted content
|
||
|
* should never be passed to `unsafeStatic`).
|
||
|
*
|
||
|
* Users must take care to ensure that adding the static string to the template
|
||
|
* results in well-formed HTML, or else templates may break unexpectedly.
|
||
|
*
|
||
|
* Static values can be changed, but they will cause a complete re-render since
|
||
|
* they effectively create a new template.
|
||
|
*/
|
||
|
export const literal = (strings, ...values) => ({
|
||
|
['_$litStatic$']: values.reduce((acc, v, idx) => acc + textFromStatic(v) + strings[idx + 1], strings[0]),
|
||
|
r: brand,
|
||
|
});
|
||
|
const stringsCache = new Map();
|
||
|
/**
|
||
|
* Wraps a lit-html template tag (`html` or `svg`) to add static value support.
|
||
|
*/
|
||
|
export const withStatic = (coreTag) => (strings, ...values) => {
|
||
|
const l = values.length;
|
||
|
let staticValue;
|
||
|
let dynamicValue;
|
||
|
const staticStrings = [];
|
||
|
const dynamicValues = [];
|
||
|
let i = 0;
|
||
|
let hasStatics = false;
|
||
|
let s;
|
||
|
while (i < l) {
|
||
|
s = strings[i];
|
||
|
// Collect any unsafeStatic values, and their following template strings
|
||
|
// so that we treat a run of template strings and unsafe static values as
|
||
|
// a single template string.
|
||
|
while (i < l &&
|
||
|
((dynamicValue = values[i]),
|
||
|
(staticValue = unwrapStaticValue(dynamicValue))) !== undefined) {
|
||
|
s += staticValue + strings[++i];
|
||
|
hasStatics = true;
|
||
|
}
|
||
|
// If the last value is static, we don't need to push it.
|
||
|
if (i !== l) {
|
||
|
dynamicValues.push(dynamicValue);
|
||
|
}
|
||
|
staticStrings.push(s);
|
||
|
i++;
|
||
|
}
|
||
|
// If the last value isn't static (which would have consumed the last
|
||
|
// string), then we need to add the last string.
|
||
|
if (i === l) {
|
||
|
staticStrings.push(strings[l]);
|
||
|
}
|
||
|
if (hasStatics) {
|
||
|
const key = staticStrings.join('$$lit$$');
|
||
|
strings = stringsCache.get(key);
|
||
|
if (strings === undefined) {
|
||
|
// Beware: in general this pattern is unsafe, and doing so may bypass
|
||
|
// lit's security checks and allow an attacker to execute arbitrary
|
||
|
// code and inject arbitrary content.
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
staticStrings.raw = staticStrings;
|
||
|
stringsCache.set(key, (strings = staticStrings));
|
||
|
}
|
||
|
values = dynamicValues;
|
||
|
}
|
||
|
return coreTag(strings, ...values);
|
||
|
};
|
||
|
/**
|
||
|
* Interprets a template literal as an HTML template that can efficiently
|
||
|
* render to and update a container.
|
||
|
*
|
||
|
* Includes static value support from `lit-html/static.js`.
|
||
|
*/
|
||
|
export const html = withStatic(coreHtml);
|
||
|
/**
|
||
|
* Interprets a template literal as an SVG template that can efficiently
|
||
|
* render to and update a container.
|
||
|
*
|
||
|
* Includes static value support from `lit-html/static.js`.
|
||
|
*/
|
||
|
export const svg = withStatic(coreSvg);
|
||
|
//# sourceMappingURL=static.js.map
|