UNPKG

chrome-devtools-frontend

Version:
149 lines (137 loc) 5.84 kB
/** * @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 */ /** * @module lit-html */ import {reparentNodes} from './dom.js'; import {TemplateProcessor} from './template-processor.js'; import {boundAttributeSuffix, lastAttributeNameRegex, marker, nodeMarker} from './template.js'; declare const trustedTypes: typeof window.trustedTypes; /** * Our TrustedTypePolicy for HTML which is declared using the 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. */ const policy = window.trustedTypes && trustedTypes!.createPolicy('lit-html', {createHTML: (s) => s}); const commentMarker = ` ${marker} `; /** * The return type of `html`, which holds a Template and the values from * interpolated expressions. */ export class TemplateResult { readonly strings: TemplateStringsArray; readonly values: readonly unknown[]; readonly type: string; readonly processor: TemplateProcessor; constructor( strings: TemplateStringsArray, values: readonly unknown[], type: string, processor: TemplateProcessor) { this.strings = strings; this.values = values; this.type = type; this.processor = processor; } /** * Returns a string of HTML used to create a `<template>` element. */ getHTML(): string { const l = this.strings.length - 1; let html = ''; let isCommentBinding = false; for (let i = 0; i < l; i++) { const s = this.strings[i]; // For each binding we want to determine the kind of marker to insert // into the template source before it's parsed by the browser's HTML // parser. The marker type is based on whether the expression is in an // attribute, text, or comment position. // * For node-position bindings we insert a comment with the marker // sentinel as its text content, like <!--{{lit-guid}}-->. // * For attribute bindings we insert just the marker sentinel for the // first binding, so that we support unquoted attribute bindings. // Subsequent bindings can use a comment marker because multi-binding // attributes must be quoted. // * For comment bindings we insert just the marker sentinel so we don't // close the comment. // // The following code scans the template source, but is *not* an HTML // parser. We don't need to track the tree structure of the HTML, only // whether a binding is inside a comment, and if not, if it appears to be // the first binding in an attribute. const commentOpen = s.lastIndexOf('<!--'); // We're in comment position if we have a comment open with no following // comment close. Because <-- can appear in an attribute value there can // be false positives. isCommentBinding = (commentOpen > -1 || isCommentBinding) && s.indexOf('-->', commentOpen + 1) === -1; // Check to see if we have an attribute-like sequence preceding the // expression. This can match "name=value" like structures in text, // comments, and attribute values, so there can be false-positives. const attributeMatch = lastAttributeNameRegex.exec(s); if (attributeMatch === null) { // We're only in this branch if we don't have a attribute-like // preceding sequence. For comments, this guards against unusual // attribute values like <div foo="<!--${'bar'}">. Cases like // <!-- foo=${'bar'}--> are handled correctly in the attribute branch // below. html += s + (isCommentBinding ? commentMarker : nodeMarker); } else { // For attributes we use just a marker sentinel, and also append a // $lit$ suffix to the name to opt-out of attribute-specific parsing // that IE and Edge do for style and certain SVG attributes. html += s.substr(0, attributeMatch.index) + attributeMatch[1] + attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] + marker; } } html += this.strings[l]; return html; } getTemplateElement(): HTMLTemplateElement { const template = document.createElement('template'); let value = this.getHTML(); if (policy !== undefined) { // this is secure because `this.strings` is a TemplateStringsArray. // TODO: validate this when // https://github.com/tc39/proposal-array-is-template-object is // implemented. value = policy.createHTML(value) as unknown as string; } template.innerHTML = value; return template; } } /** * A TemplateResult for SVG fragments. * * This class wraps HTML in an `<svg>` tag in order to parse its contents in the * SVG namespace, then modifies the template to remove the `<svg>` tag so that * clones only container the original fragment. */ export class SVGTemplateResult extends TemplateResult { getHTML(): string { return `<svg>${super.getHTML()}</svg>`; } getTemplateElement(): HTMLTemplateElement { const template = super.getTemplateElement(); const content = template.content; const svgElement = content.firstChild!; content.removeChild(svgElement); reparentNodes(content, svgElement.firstChild); return template; } }