@polymer/polymer
Version:
The Polymer library makes it easy to create your own web components. Give your element some markup and properties, and then use it on a site. Polymer provides features like dynamic templates and data binding to reduce the amount of boilerplate you need to
177 lines (169 loc) • 6.32 kB
JavaScript
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
import './boot.js';
/**
* Our TrustedTypePolicy for HTML which is declared using the Polymer html
* template tag function.
*
* That HTML is a developer-authored constant, and is parsed with innerHTML
* before any untrusted expressions have been mixed in. Therefor it is
* considered safe by construction.
*
* @type {!TrustedTypePolicy|undefined}
*/
const policy = window.trustedTypes &&
trustedTypes.createPolicy('polymer-html-literal', {createHTML: (s) => s});
/**
* Class representing a static string value which can be used to filter
* strings by asseting that they have been created via this class. The
* `value` property returns the string passed to the constructor.
*/
class LiteralString {
/**
* @param {!ITemplateArray} strings Constant parts of tagged template literal
* @param {!Array<*>} values Variable parts of tagged template literal
*/
constructor(strings, values) {
assertValidTemplateStringParameters(strings, values);
const string = values.reduce(
(acc, v, idx) => acc + literalValue(v) + strings[idx + 1], strings[0]);
/** @type {string} */
this.value = string.toString();
}
/**
* @return {string} LiteralString string value
* @override
*/
toString() {
return this.value;
}
}
/**
* @param {*} value Object to stringify into HTML
* @return {string} HTML stringified form of `obj`
*/
function literalValue(value) {
if (value instanceof LiteralString) {
return /** @type {!LiteralString} */(value).value;
} else {
throw new Error(
`non-literal value passed to Polymer's htmlLiteral function: ${value}`
);
}
}
/**
* @param {*} value Object to stringify into HTML
* @return {string} HTML stringified form of `obj`
*/
function htmlValue(value) {
if (value instanceof HTMLTemplateElement) {
// This might be an mXSS risk – mainly in the case where this template
// contains untrusted content that was believed to be sanitized.
// However we can't just use the XMLSerializer here because it misencodes
// `>` characters inside style tags.
// For an example of an actual case that hit this encoding issue,
// see b/198592167
return /** @type {!HTMLTemplateElement } */(value).innerHTML;
} else if (value instanceof LiteralString) {
return literalValue(value);
} else {
throw new Error(
`non-template value passed to Polymer's html function: ${value}`);
}
}
/**
* A template literal tag that creates an HTML <template> element from the
* contents of the string.
*
* This allows you to write a Polymer Template in JavaScript.
*
* Templates can be composed by interpolating `HTMLTemplateElement`s in
* expressions in the JavaScript template literal. The nested template's
* `innerHTML` is included in the containing template. The only other
* values allowed in expressions are those returned from `htmlLiteral`
* which ensures only literal values from JS source ever reach the HTML, to
* guard against XSS risks.
*
* All other values are disallowed in expressions to help prevent XSS
* attacks; however, `htmlLiteral` can be used to compose static
* string values into templates. This is useful to compose strings into
* places that do not accept html, like the css text of a `style`
* element.
*
* Example:
*
* static get template() {
* return html`
* <style>:host{ content:"..." }</style>
* <div class="shadowed">${this.partialTemplate}</div>
* ${super.template}
* `;
* }
* static get partialTemplate() { return html`<span>Partial!</span>`; }
*
* @param {!ITemplateArray} strings Constant parts of tagged template literal
* @param {...*} values Variable parts of tagged template literal
* @return {!HTMLTemplateElement} Constructed HTMLTemplateElement
*/
export const html = function html(strings, ...values) {
assertValidTemplateStringParameters(strings, values);
const template =
/** @type {!HTMLTemplateElement} */ (document.createElement('template'));
let value = values.reduce(
(acc, v, idx) => acc + htmlValue(v) + strings[idx + 1], strings[0]);
if (policy) {
value = policy.createHTML(value);
}
template.innerHTML = value;
return template;
};
/**
* @param {!ITemplateArray} strings Constant parts of tagged template literal
* @param {!Array<*>} values Array of values from quasis
*/
const assertValidTemplateStringParameters = (strings, values) => {
// Note: if/when https://github.com/tc39/proposal-array-is-template-object
// is standardized, use that instead when available, as it can perform an
// unforgable check (though of course, the function itself can be forged).
if (!Array.isArray(strings) || !Array.isArray(strings.raw) ||
(values.length !== strings.length - 1)) {
// This is either caused by a browser bug, a compiler bug, or someone
// calling the html template tag function as a regular function.
//
throw new TypeError('Invalid call to the html template tag');
}
};
/**
* An html literal tag that can be used with `html` to compose.
* a literal string.
*
* Example:
*
* static get template() {
* return html`
* <style>
* :host { display: block; }
* ${this.styleTemplate()}
* </style>
* <div class="shadowed">${staticValue}</div>
* ${super.template}
* `;
* }
* static get styleTemplate() {
* return htmlLiteral`.shadowed { background: gray; }`;
* }
*
* @param {!ITemplateArray} strings Constant parts of tagged template literal
* @param {...*} values Variable parts of tagged template literal
* @return {!LiteralString} Constructed literal string
*/
export const htmlLiteral = function(strings, ...values) {
return new LiteralString(strings, values);
};