diff --git a/README.md b/README.md
index 9b0d35e..b32d1bd 100644
--- a/README.md
+++ b/README.md
@@ -371,7 +371,10 @@ to
```html
-
+
```
diff --git a/a-tag-test.html b/a-tag-test.html
index 5fd482b..653fdf6 100644
--- a/a-tag-test.html
+++ b/a-tag-test.html
@@ -20,6 +20,7 @@
+
` without any data
binding.
+If you find that something is rejected that is innocuous, file a [bug][issues].
+Polymer-resin whitelists elements and attributes, and our whitelist is probably
+incomplete.
+
## End-to-end safety
TODO: talk about using in conjunction with JSConformance and `--polymer_pass` to
@@ -234,8 +298,10 @@ check sanitariness of JS and sources of safe html types.
[safe-url]: https://google.github.io/closure-library/api/goog.html.SafeUrl.html
[safe-html-types]: https://github.com/google/safe-html-types/blob/master/doc/safehtml-types.md
[html-import]: https://www.webcomponents.org/community/articles/introduction-to-html-imports
-[allow-ident-prefix]: https://github.com/Polymer/polymer-resin/blob/6dbc44f9e5484771e483fdc0a3909f21eb1d99f9/polymer-resin.js#L51-L63
[a.c.e.]: https://en.wikipedia.org/wiki/Arbitrary_code_execution
[contracts-a-href]: https://github.com/Polymer/polymer-resin/blob/ff7f58f00ec0794517ecca11a801a2a7e6c04e84/lib/contracts/contracts.js#L296-L302
[closure-library]: https://github.com/google/closure-library
[contract-types]: https://github.com/google/safe-html-types/blob/master/doc/safehtml-types.md#types
+[csp-report-only]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
+[releases]: https://github.com/Polymer/polymer-resin/releases
+[issues]: https://github.com/Polymer/polymer-resin/issues
diff --git a/identifier-test.html b/identifier-test.html
index dea064b..a4bc0ab 100644
--- a/identifier-test.html
+++ b/identifier-test.html
@@ -22,7 +22,9 @@
Identifier Test
diff --git a/one-attr-binding-test.html b/one-attr-binding-test.html
index 3131b13..90e442a 100644
--- a/one-attr-binding-test.html
+++ b/one-attr-binding-test.html
@@ -20,6 +20,7 @@
+
One Attr Binding Test
diff --git a/one-late-attr-binding-test.html b/one-late-attr-binding-test.html
index 2f5463b..2150519 100644
--- a/one-late-attr-binding-test.html
+++ b/one-late-attr-binding-test.html
@@ -20,6 +20,7 @@
+
One Late Attr Binding Test
diff --git a/polymer-resin.js b/polymer-resin.js
index 3ab1492..6875326 100644
--- a/polymer-resin.js
+++ b/polymer-resin.js
@@ -12,14 +12,17 @@
* http://polymer.github.io/PATENTS.txt
*/
-// TODO: check that this is running early in the application booting
-// process. Maybe check that web components are not ready.
-// Will that cause problems if parts of the web components API are defined
-// natively instead of polyfilled?
+"use strict";
-goog.provide('security.polymer_resin.UNSAFE_passThruDisallowedValues');
-goog.provide('security.polymer_resin.allowIdentifierWithPrefix');
-goog.provide('security.polymer_resin.setReportHandler');
+
+/**
+ * @fileoverview
+ * Mitigates XSS in Polymer applications by intercepting and vetting
+ * results of data binding expressions before they reach browser internals.
+ */
+
+
+goog.provide('security.polymer_resin');
goog.require('goog.dom.NodeType');
goog.require('goog.html.SafeHtml');
@@ -37,6 +40,82 @@ goog.require('security.polymer_resin.classifyElement');
goog.require('security.polymer_resin.hintUsesDeprecatedRegisterElement');
+/**
+ * Type for a configuration object that can be passed to install.
+ *
+ *
+ *
+ * When {@code UNSAFE_passThruDisallowedValues} is {@code true},
+ * disallowed values will not be replaced so may reach
+ * unsafe browser sinks resulting in a security vulnerability.
+ *
+ * This mode is provided only to allow testing of an application
+ * to find and compile the kinds of false positives triggered by
+ * an application that is being migrated to use polymer resin.
+ *
+ * This MUST NOT be used in production with end users and
+ * MUST NOT be set based on any attacker-controllable state like
+ * URL parameters.
+ *
+ * If you never specify this property, you are safer.
+ *
+ *
+ * When not in goog.DEBUG mode, this is ignored.
+ *
+ *
+ *
+ * {@code allowedIdentifierPrefixes} specifies prefixes for allowed values
+ * for attributes with type IDENTIFIER.
+ *
+ * By default, only the empty identifier is allowed.
+ *
+ *
+ *
+ * {@code reportHandler} is a callback that receives reports about rejected
+ * values and module status
+ *
+ * By default, if {@code goog.DEBUG} is false at init time, reportHandler is
+ * never called, and if {@code goog.DEBUG} is true at init time, reportHandler
+ * logs to the JS developer console.
+ *
+ * Assuming it is enabled, either via {@code goog.DEBUG} or an explicit call to
+ * this setter, then it is called on every rejected value, and on major events
+ * like module initialization.
+ *
+ * This may be used to identify false positives during debugging; to compile
+ * lists of false positives when migrating; or to gather telemetry by
+ * compiling a table summarizing disallowed value reports.
+ *
+ * @typedef {{
+ * 'UNSAFE_passThruDisallowedValues': (?boolean | undefined),
+ * 'allowedIdentifierPrefixes': (?Array. | undefined),
+ * 'reportHandler': (security.polymer_resin.ReportHandler | undefined)
+ * }}
+ */
+security.polymer_resin.Configuration;
+
+
+/**
+ * A function that takes (isDisallowedValue, printfFormatString, printfArgs).
+ * The arguments are ready to forward straight to the console with minimal
+ * overhead.
+ *
+ * If isDisallowedValue is true then the args have the printArgs have the form
+ * [contextNodeName, nodeName, attributeOrPropertyName, disallowedValue].
+ *
+ * The context node is the element being manipulated, or if nodeName is
+ * {@code "#text"},
+ * then contextNode is the parent of the text node being manipulated, so
+ * the contextNode should always be an element or document fragment.
+ * In that case, attributeOrPropertyName can be ignored.
+ *
+ * If null then reporting is disabled.
+ *
+ * @typedef {?function (boolean, string, ...*)}
+ */
+security.polymer_resin.ReportHandler;
+
+
/**
* Maps Safe HTML types to handlers.
*
@@ -47,7 +126,7 @@ goog.require('security.polymer_resin.hintUsesDeprecatedRegisterElement');
* unwrap: ?function(?):string
* }}
*/
-var ValueHandler;
+security.polymer_resin.ValueHandler;
/**
@@ -68,8 +147,9 @@ var ValueHandler;
* When not in goog.DEBUG mode, this is a no-op.
*
* @param {boolean} enable Pass true to enable UNSAFE mode.
+ * @private
*/
-security.polymer_resin.UNSAFE_passThruDisallowedValues = function (enable) {
+security.polymer_resin.UNSAFE_passThruDisallowedValues_ = function (enable) {
if (goog.DEBUG) {
security.polymer_resin.allowUnsafeValues_ = enable === true;
}
@@ -87,8 +167,9 @@ security.polymer_resin.UNSAFE_passThruDisallowedValues = function (enable) {
*
* @param {string} prefix A string prefix. The empty string is a prefix
* of all strings.
+ * @private
*/
-security.polymer_resin.allowIdentifierWithPrefix = function (prefix) {
+security.polymer_resin.allowIdentifierWithPrefix_ = function (prefix) {
security.polymer_resin.allowedIdentifierPattern_ = new RegExp(
security.polymer_resin.allowedIdentifierPattern_.source
+ '|^' + goog.string.regExpEscape(prefix));
@@ -97,51 +178,18 @@ security.polymer_resin.allowIdentifierWithPrefix = function (prefix) {
/**
* Sets a callback to receive reports about rejected values and module status.
*
- *
- * By default, if {@code goog.DEBUG} is false at init time, reportHandler is
- * never called, and if {@code goog.DEBUG} is true at init time, reportHandler
- * logs to the JS developer console.
- *
- * Assuming it is enabled, either via {@code goog.DEBUG} or an explicit call to
- * this setter, then it is called on every rejected value, and on major events
- * like module initialization.
- *
- * This may be used to identify false positives during debugging; to compile
- * lists of false positives when migrating; or to gather telemetry by
- *
- * @param {?function (boolean, string, ...*)} reportHandler
- * A function that takes (isDisallowedValue, printfFormatString, printfArgs).
- * The arguments are ready to forward straight to the console with minimal
- * overhead.
- *
- * If isDisallowedValue is true then the args have the printArgs have the form
- * [contextNodeName, nodeName, attributeOrPropertyName, disallowedValue].
- *
- * The context node is the element being manipulated, or if nodeName is
- * {@code "#text"},
- * then contextNode is the parent of the text node being manipulated, so
- * the contextNode should always be an element or document fragment.
- * In that case, attributeOrPropertyName can be ignored.
- *
- * If reportHandler is null then reporting is disabled.
+ * @param {?security.polymer_resin.ReportHandler} reportHandler
+ * @private
*/
-security.polymer_resin.setReportHandler = function (reportHandler) {
+security.polymer_resin.setReportHandler_ = function (reportHandler) {
security.polymer_resin.reportHandler_ = reportHandler || null;
};
goog.exportSymbol(
- 'security.polymer_resin.UNSAFE_passThruDisallowedValues',
- security.polymer_resin.UNSAFE_passThruDisallowedValues);
-
-goog.exportSymbol(
- 'security.polymer_resin.allowIdentifierWithPrefix',
- security.polymer_resin.allowIdentifierWithPrefix);
-
-goog.exportSymbol(
- 'security.polymer_resin.setReportHandler',
- security.polymer_resin.setReportHandler);
+ 'security.polymer_resin.install',
+ security.polymer_resin.install);
/**
@@ -166,508 +214,544 @@ security.polymer_resin.allowUnsafeValues_ = false;
* Undefined means never set (see default behavior under docs for
* setter above), null means disabled.
*
- * @type {function (boolean, string, ...*)|null|undefined}
+ * @type {security.polymer_resin.ReportHandler|null|undefined}
* @private
*/
security.polymer_resin.reportHandler_ = undefined;
-(function () {
- "use strict";
-
- function initResin() {
- if (goog.DEBUG && security.polymer_resin.reportHandler_ === undefined
- && typeof console !== 'undefined') {
- security.polymer_resin.reportHandler_ =
- function (isViolation, formatString, var_args) {
- var consoleArgs = [formatString];
- for (var i = 2, n = arguments.length; i < n; ++i) {
- consoleArgs[i - 1] = arguments[i];
- }
- if (isViolation) {
- console.warn.apply(console, consoleArgs);
- } else {
- console.log.apply(console, consoleArgs);
- }
- };
- }
-
- // TODO: check not in IE quirks mode.
- if (security.polymer_resin.reportHandler_) {
- // Emitting this allows an integrator to tell where resin is
- // installing relative to other code that is running in the app.
- security.polymer_resin.reportHandler_(false, 'initResin');
+/**
+ * Start polymer resin.
+ * This must be done before the first application element is instantiated
+ * (see getting-started.md for details).
+ *
+ * @param {?security.polymer_resin.Configuration=} opt_config
+ * An optional configuration object.
+ */
+security.polymer_resin.install = function (opt_config) {
+ // TODO: What to do if called multiple times?
+ if (opt_config) {
+ var configUnsafePassThruDisallowedValues =
+ opt_config['UNSAFE_passThruDisallowedValues'];
+ var configAllowedIdentifierPrefixes =
+ opt_config['allowedIdentifierPrefixes'];
+ var configReportHandler = opt_config['reportHandler'];
+
+ if (configUnsafePassThruDisallowedValues != null) {
+ security.polymer_resin.UNSAFE_passThruDisallowedValues_(
+ configUnsafePassThruDisallowedValues);
}
- /**
- * @param {string} name
- * @this {!Element}
- * @return {*} null indicates unknown.
- */
- function getAttributeValue(name) {
- var value = this.getAttribute(name);
- if (!value || /[\[\{]/.test(name)) {
- // If a value contains '[' or '{',
- // assume that it is a bound attribute for which
- // we have not yet computed a value.
- // The consumer of this function cares only about
- // keyword values, so this loses us nothing.
- return null;
+ if (configAllowedIdentifierPrefixes) {
+ for (var i = 0, n = configAllowedIdentifierPrefixes.length; i < n; ++i) {
+ security.polymer_resin.allowIdentifierWithPrefix_(
+ configAllowedIdentifierPrefixes[i]);
}
- return value;
}
- /**
- * Uncustomized versions of custom-builtin objects.
- * {@type Object.}
- */
- var uncustomizedProxies = {};
+ if (configReportHandler !== undefined) {
+ security.polymer_resin.setReportHandler_(configReportHandler);
+ }
+ }
- /**
- * An element that only has global attribute aliases.
- * @type {!Element}
- */
- var VANILLA_HTML_ELEMENT_ = document.createElement('polyresinuncustomized');
+ if (goog.DEBUG && security.polymer_resin.reportHandler_ === undefined
+ && typeof console !== 'undefined') {
+ security.polymer_resin.reportHandler_ =
+ function (isViolation, formatString, var_args) {
+ var consoleArgs = [formatString];
+ for (var i = 2, n = arguments.length; i < n; ++i) {
+ consoleArgs[i - 1] = arguments[i];
+ }
+ if (isViolation) {
+ console.warn.apply(console, consoleArgs);
+ } else {
+ console.log.apply(console, consoleArgs);
+ }
+ };
+ }
- /**
- * @param {!Element} element
- * @return {!Element}
- */
- function getUncustomizedProxy(element) {
- var elementName = element.localName;
- var customBuiltinElementName = element.getAttribute('is');
-
- if (!customBuiltinElementName) {
- // TODO: Test what happens when a Polymer element defines property
- // constructor.
- // Possible workaround:
- // 1. assert element instanceof element.constructor or
- // 2. use Object.getPrototypeOf(element).constructor.
- /** @type {security.polymer_resin.CustomElementClassification} */
- var classification = security.polymer_resin.classifyElement(
- elementName, /** @type{!Function} */(element.constructor));
- if (classification
- === security.polymer_resin.CustomElementClassification.CUSTOM) {
- // Custom elements have a layer between them and their prototype, so
- // we should not treat own properties assigned in the custom element's
- // constructor as builtin attribute aliases.
- return VANILLA_HTML_ELEMENT_;
- }
- }
+ // TODO: check not in IE quirks mode.
+ if (security.polymer_resin.reportHandler_) {
+ // Emitting this allows an integrator to tell where resin is
+ // installing relative to other code that is running in the app.
+ security.polymer_resin.reportHandler_(false, 'initResin');
+ }
- // For normal custom elements, the builtin property setters are defined
- // on a prototype, so we can check hasOwnProperty.
- // For custom builtin properties we can't do that since the object is
- // a builtin object that then has custom stuff mixed in.
- // We use a non-customized version of the builtin and check that.
- var uncustomizedProxy = uncustomizedProxies[elementName];
- if (!uncustomizedProxy) {
- uncustomizedProxy = uncustomizedProxies[elementName] =
- document.createElement(elementName);
- }
- return uncustomizedProxy;
+ /**
+ * @param {string} name
+ * @this {!Element}
+ * @return {*} null indicates unknown.
+ */
+ function getAttributeValue(name) {
+ var value = this.getAttribute(name);
+ if (!value || /[\[\{]/.test(name)) {
+ // If a value contains '[' or '{',
+ // assume that it is a bound attribute for which
+ // we have not yet computed a value.
+ // The consumer of this function cares only about
+ // keyword values, so this loses us nothing.
+ return null;
}
+ return value;
+ }
- /**
- * Filters and unwraps new property values in preparation for them
- * being attached to custom elements.
- *
- * @param {!Node} node a custom element, builtin element, or text node.
- * @param {string} name the name of the property
- * @param {string} type whether name is a 'property' or 'attribute' name.
- * @param {*} value a value that may have originated outside this document's
- * origin.
- * @return {*} a value that is safe to embed in this document's origin.
- */
- function sanitize(node, name, type, value) {
- if (!value) {
- // We allow clearing properties and initial values.
- // This does mean that the following strings could be introduced into
- // safe string contexts:
- // "", "null", "undefined", "0", "NaN", "false"
- // I consider these values innocuous.
- return value;
- }
-
- var nodeType = node.nodeType;
- if (nodeType !== goog.dom.NodeType.ELEMENT) {
- // TODO: does polymer use CDATA sections?
- if (nodeType === goog.dom.NodeType.TEXT) {
- // Whitelist and handle text node interpolation by checking
- // the content type of the parent node.
- var parentElement = node.parentElement;
- if (parentElement
- && parentElement.nodeType === goog.dom.NodeType.ELEMENT) {
- var parentElementName = parentElement.localName;
- var parentClassification = security.polymer_resin.classifyElement(
- parentElementName,
- /** @type{!Function} */(parentElement.constructor));
- var allowText = false;
- switch (parentClassification) {
- case security.polymer_resin.CustomElementClassification.BUILTIN:
- case security.polymer_resin.CustomElementClassification.LEGACY:
- var contentType = security.html.contracts.contentTypeForElement(
- parentElementName);
- allowText = contentType
- === security.html.contracts.ElementContentType.SAFE_HTML;
- break;
- case security.polymer_resin.CustomElementClassification.CUSTOMIZABLE:
- case security.polymer_resin.CustomElementClassification.CUSTOM:
- // Allow text in custom elements.
- allowText = true;
- break;
- }
- if (allowText) {
- return (
- !!(value && value.implementsGoogStringTypedString)
- ? (/** @type {!goog.string.TypedString} */(value))
- .getTypedStringValue()
- : String(value));
- }
- }
- }
+ /**
+ * Uncustomized versions of custom-builtin objects.
+ * {@type Object.}
+ */
+ var uncustomizedProxies = {};
- if (security.polymer_resin.reportHandler_) {
- security.polymer_resin.reportHandler_(
- true, 'Failed to sanitize %s %s%s node to value %O',
- node.parentElement && node.parentElement.nodeName,
- '#text', '', value);
- }
+ /**
+ * An element that only has global attribute aliases.
+ * @type {!Element}
+ */
+ var VANILLA_HTML_ELEMENT_ = document.createElement('polyresinuncustomized');
- return INNOCUOUS_STRING;
+ /**
+ * @param {!Element} element
+ * @return {!Element}
+ */
+ function getUncustomizedProxy(element) {
+ var elementName = element.localName;
+ var customBuiltinElementName = element.getAttribute('is');
+
+ if (!customBuiltinElementName) {
+ // TODO: Test what happens when a Polymer element defines property
+ // constructor.
+ // Possible workaround:
+ // 1. assert element instanceof element.constructor or
+ // 2. use Object.getPrototypeOf(element).constructor.
+ /** @type {security.polymer_resin.CustomElementClassification} */
+ var classification = security.polymer_resin.classifyElement(
+ elementName, /** @type{!Function} */(element.constructor));
+ if (classification
+ === security.polymer_resin.CustomElementClassification.CUSTOM) {
+ // Custom elements have a layer between them and their prototype, so
+ // we should not treat own properties assigned in the custom element's
+ // constructor as builtin attribute aliases.
+ return VANILLA_HTML_ELEMENT_;
}
+ }
- var element = /** @type {!Element} */(node);
- var elementName = element.localName;
+ // For normal custom elements, the builtin property setters are defined
+ // on a prototype, so we can check hasOwnProperty.
+ // For custom builtin properties we can't do that since the object is
+ // a builtin object that then has custom stuff mixed in.
+ // We use a non-customized version of the builtin and check that.
+ var uncustomizedProxy = uncustomizedProxies[elementName];
+ if (!uncustomizedProxy) {
+ uncustomizedProxy = uncustomizedProxies[elementName] =
+ document.createElement(elementName);
+ }
+ return uncustomizedProxy;
+ }
- // Check whether an uncustomized version has an own property.
- var elementProxy = getUncustomizedProxy(element);
+ /**
+ * Filters and unwraps new property values in preparation for them
+ * being attached to custom elements.
+ *
+ * @param {!Node} node a custom element, builtin element, or text node.
+ * @param {string} name the name of the property
+ * @param {string} type whether name is a 'property' or 'attribute' name.
+ * @param {*} value a value that may have originated outside this document's
+ * origin.
+ * @return {*} a value that is safe to embed in this document's origin.
+ */
+ function sanitize(node, name, type, value) {
+ if (!value) {
+ // We allow clearing properties and initial values.
+ // This does mean that the following strings could be introduced into
+ // safe string contexts:
+ // "", "null", "undefined", "0", "NaN", "false"
+ // I consider these values innocuous.
+ return value;
+ }
- switch (type) {
- case 'attribute':
- // TODO: figure out why attr-property-aliasing test doesn't seem to be
- // reaching this branch but running under Polymer 1.7 inside
- // polygerrit does.
- var propName = security.html.namealiases.attrToProperty(name);
- if (propName in elementProxy) {
- break;
- }
- return value;
- case 'property':
- if (name in elementProxy) {
- break;
+ var nodeType = node.nodeType;
+ if (nodeType !== goog.dom.NodeType.ELEMENT) {
+ // TODO: does polymer use CDATA sections?
+ if (nodeType === goog.dom.NodeType.TEXT) {
+ // Whitelist and handle text node interpolation by checking
+ // the content type of the parent node.
+ var parentElement = node.parentElement;
+ if (parentElement
+ && parentElement.nodeType === goog.dom.NodeType.ELEMENT) {
+ var parentElementName = parentElement.localName;
+ var parentClassification = security.polymer_resin.classifyElement(
+ parentElementName,
+ /** @type{!Function} */(parentElement.constructor));
+ var allowText = false;
+ switch (parentClassification) {
+ case security.polymer_resin.CustomElementClassification.BUILTIN:
+ case security.polymer_resin.CustomElementClassification.LEGACY:
+ var contentType = security.html.contracts.contentTypeForElement(
+ parentElementName);
+ allowText = contentType
+ === security.html.contracts.ElementContentType.SAFE_HTML;
+ break;
+ case security.polymer_resin.CustomElementClassification.CUSTOMIZABLE:
+ case security.polymer_resin.CustomElementClassification.CUSTOM:
+ // Allow text in custom elements.
+ allowText = true;
+ break;
}
- var worstCase =
- security.html.namealiases.specialPropertyNameWorstCase(name);
- if (worstCase && worstCase in elementProxy) {
- break;
+ if (allowText) {
+ return (
+ !!(value && value.implementsGoogStringTypedString)
+ ? (/** @type {!goog.string.TypedString} */(value))
+ .getTypedStringValue()
+ : String(value));
}
- return value;
- default:
- throw new Error(type + ': ' + typeof type);
- }
-
- /**
- * The HTML attribute name.
- * @type {string}
- */
- var attrName =
- (type == 'attribute') // Closed set tested in switch above
- // toLowerCase is Turkish-I safe because
- // www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.tolowercase
- // says
- // """
- // 5. For each code point c in cpList, if the Unicode Character
- // Database provides a LANGUAGE INSENSITIVE lower case equivalent of c
- // then replace c in cpList with that equivalent code point(s).
- // ""
- // modulo bugs in old versions of Rhino.
- ? name.toLowerCase()
- : security.html.namealiases.propertyToAttr(name);
-
- /** @type {?security.html.contracts.AttrType} */
- var attrType = security.html.contracts.typeOfAttribute(
- elementName, attrName, goog.bind(getAttributeValue, element));
- /** @type {string} */
- var safeValue = INNOCUOUS_STRING;
- if (attrType != null) {
- /** @type {!ValueHandler} */
- var valueHandler = VALUE_HANDLERS_[attrType];
- if (valueHandler.typeToUnwrap
- && value instanceof valueHandler.typeToUnwrap) {
- return valueHandler.unwrap(value);
- }
- var stringValue =
- !!(value && value.implementsGoogStringTypedString)
- ? (/** @type {!goog.string.TypedString} */(value))
- .getTypedStringValue()
- : String(value);
- safeValue =
- valueHandler.filter
- ? valueHandler.filter(elementName, attrName, stringValue)
- : stringValue;
- if (safeValue !== valueHandler.safeReplacement) {
- return safeValue;
}
}
+
if (security.polymer_resin.reportHandler_) {
security.polymer_resin.reportHandler_(
- true, 'Failed to sanitize in %s: <%s %s="%O">',
- elementName, elementName, attrName, value);
+ true, 'Failed to sanitize %s %s%s node to value %O',
+ node.parentElement && node.parentElement.nodeName,
+ '#text', '', value);
}
- return safeValue;
+ return security.polymer_resin.INNOCUOUS_STRING_;
}
- // Now, install the sanitizer.
- // There are two code-paths below for Polymer V1 and V2.
- // The code could be consolidated, except that
- // 1. In both cases I want to first delegate to any previously
- // registered handler.
- // 2. If I tried to express the V1 logic in terms of V2, I would need
- // to thread the info object through somehow hacky.
- // 3. I don't want to express the V2 logic in terms of V1 since the V1
- // will hopefully eventually go away entirely.
- // So I just duplicate the logic which goes through these steps:
- // 1. fetch any previously declared function
- // 2. define an adapter
- // 3. install the adapter
- // 4. sanity check that the adapter was installed.
- if (/^1\./.test(Polymer.version)) {
- // In Polymer v1, the Polymer(...) method uses the deprecated
- // document.registerElement instead of window.customElements.
- security.polymer_resin.hintUsesDeprecatedRegisterElement();
-
- // sanitizeDOMValue is not defined for v1.
- // See https://github.com/Polymer/polymer/issues/4138
- var origCompute = Polymer.Base._computeFinalAnnotationValue;
- var computeFinalAnnotationSafeValue =
- function computeFinalAnnotationValue(node, property, value, info) {
- var finalValue = origCompute.call(
- Polymer.Base, node, property, value, info);
- var type = 'property';
- var name;
- if (info && info.propertyName) {
- name = info.propertyName;
- } else {
- name = property;
- type = info && info.kind || 'property';
- }
-
- var safeValue = sanitize(node, name, type, finalValue);
+ var element = /** @type {!Element} */(node);
+ var elementName = element.localName;
- return (
- security.polymer_resin.allowUnsafeValues_
- ? finalValue : safeValue);
- };
- Polymer.Base._computeFinalAnnotationValue =
- computeFinalAnnotationSafeValue;
- if (Polymer.Base._computeFinalAnnotationValue
- !== computeFinalAnnotationSafeValue) {
- // We're in use strict, so assignment should fail-fast, but
- // this is cheap.
- throw new Error(
- 'Cannot replace _computeFinalAnnotationValue. Is Polymer frozen?');
- }
- } else {
- var origSanitize = Polymer.sanitizeDOMValue;
- var sanitizeDOMValue =
- function sanitizeDOMValue(value, name, type, node) {
- var origSanitizedValue = origSanitize
- ? origSanitize.call(Polymer, value, name, type, node)
- : value;
- var safeValue = sanitize(node, name, type, origSanitizedValue);
+ // Check whether an uncustomized version has an own property.
+ var elementProxy = getUncustomizedProxy(element);
- return (
- security.polymer_resin.allowUnsafeValues_
- ? origSanitizedValue : safeValue);
- };
- Polymer.sanitizeDOMValue = sanitizeDOMValue;
- if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
- // We're in use strict, so assignment should fail-fast, but
- // this is cheap.
- throw new Error(
- 'Cannot install sanitizeDOMValue. Is Polymer frozen?');
- }
+ switch (type) {
+ case 'attribute':
+ // TODO: figure out why attr-property-aliasing test doesn't seem to be
+ // reaching this branch but running under Polymer 1.7 inside
+ // polygerrit does.
+ var propName = security.html.namealiases.attrToProperty(name);
+ if (propName in elementProxy) {
+ break;
+ }
+ return value;
+ case 'property':
+ if (name in elementProxy) {
+ break;
+ }
+ var worstCase =
+ security.html.namealiases.specialPropertyNameWorstCase(name);
+ if (worstCase && worstCase in elementProxy) {
+ break;
+ }
+ return value;
+ default:
+ throw new Error(type + ': ' + typeof type);
}
- }
+ /**
+ * The HTML attribute name.
+ * @type {string}
+ */
+ var attrName =
+ (type == 'attribute') // Closed set tested in switch above
+ // toLowerCase is Turkish-I safe because
+ // www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.tolowercase
+ // says
+ // """
+ // 5. For each code point c in cpList, if the Unicode Character
+ // Database provides a LANGUAGE INSENSITIVE lower case equivalent of c
+ // then replace c in cpList with that equivalent code point(s).
+ // ""
+ // modulo bugs in old versions of Rhino.
+ ? name.toLowerCase()
+ : security.html.namealiases.propertyToAttr(name);
+
+ /** @type {?security.html.contracts.AttrType} */
+ var attrType = security.html.contracts.typeOfAttribute(
+ elementName, attrName, goog.bind(getAttributeValue, element));
+ /** @type {string} */
+ var safeValue = security.polymer_resin.INNOCUOUS_STRING_;
+ if (attrType != null) {
+ /** @type {!security.polymer_resin.ValueHandler} */
+ var valueHandler = security.polymer_resin.VALUE_HANDLERS_[attrType];
+ if (valueHandler.typeToUnwrap
+ && value instanceof valueHandler.typeToUnwrap) {
+ return valueHandler.unwrap(value);
+ }
+ var stringValue =
+ !!(value && value.implementsGoogStringTypedString)
+ ? (/** @type {!goog.string.TypedString} */(value))
+ .getTypedStringValue()
+ : String(value);
+ safeValue =
+ valueHandler.filter
+ ? valueHandler.filter(elementName, attrName, stringValue)
+ : stringValue;
+ if (safeValue !== valueHandler.safeReplacement) {
+ return safeValue;
+ }
+ }
+ if (security.polymer_resin.reportHandler_) {
+ security.polymer_resin.reportHandler_(
+ true, 'Failed to sanitize in %s: <%s %s="%O">',
+ elementName, elementName, attrName, value);
+ }
- /**
- * Analogous to goog.html.SafeUrl.INNOCUOUS_STRING but
- * used for const strings and safe html types that
- * do not have their own defined.
- * @const
- */
- var INNOCUOUS_STRING = 'zClosurez';
+ return safeValue;
+ }
- /** @const */
- var INNOCUOUS_SCRIPT = '/*zClosurez*/';
+ // Now, install the sanitizer.
+ // There are two code-paths below for Polymer V1 and V2.
+ // The code could be consolidated, except that
+ // 1. In both cases I want to first delegate to any previously
+ // registered handler.
+ // 2. If I tried to express the V1 logic in terms of V2, I would need
+ // to thread the info object through somehow hacky.
+ // 3. I don't want to express the V2 logic in terms of V1 since the V1
+ // will hopefully eventually go away entirely.
+ // So I just duplicate the logic which goes through these steps:
+ // 1. fetch any previously declared function
+ // 2. define an adapter
+ // 3. install the adapter
+ // 4. sanity check that the adapter was installed.
+ if (/^1\./.test(Polymer.version)) {
+ // In Polymer v1, the Polymer(...) method uses the deprecated
+ // document.registerElement instead of window.customElements.
+ security.polymer_resin.hintUsesDeprecatedRegisterElement();
+
+ // sanitizeDOMValue is not defined for v1.
+ // See https://github.com/Polymer/polymer/issues/4138
+ var origCompute = Polymer.Base._computeFinalAnnotationValue;
+ var computeFinalAnnotationSafeValue =
+ function computeFinalAnnotationValue(node, property, value, info) {
+ var finalValue = origCompute.call(
+ Polymer.Base, node, property, value, info);
+ var type = 'property';
+ var name;
+ if (info && info.propertyName) {
+ name = info.propertyName;
+ } else {
+ name = property;
+ type = info && info.kind || 'property';
+ }
- /**
- * @type {!Array.}
- */
- var VALUE_HANDLERS_ = [];
- VALUE_HANDLERS_[security.html.contracts.AttrType.NONE] = {
- // A function that maps values to safe values or that
- // returns a safe replacement value.
- filter: null,
- // A safe value that indicates a problem likely occurred
- // so an event is worth logging.
- safeReplacement: null,
- // Constructor for a subtype of TypedString to unwrap
- typeToUnwrap: null,
- // Unwraps values that are instanceof typeToUnwrap
- unwrap: null
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_HTML] = {
- // Assigning a string to srchtml is the same as
- // assigning a text node.
- filter: (
- /**
- * Converts plain text to HTML that parses to a text node with
- * equivalent content.
- *
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string}
- */
- function plainTextToHtml(e, a, v) {
- return goog.string.htmlEscape(v);
- }),
- safeReplacement: null,
- typeToUnwrap: goog.html.SafeHtml,
- unwrap: goog.html.SafeHtml.unwrap
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_URL] = {
- filter: (
- /**
- * Allows safe URLs through, but rejects unsafe ones.
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string}
- */
- function allowSafeUrl(e, a, v) {
- // TODO: Can we do without creating a SafeUrl instance?
- return goog.html.SafeUrl.sanitize(v).getTypedStringValue();
- }),
- safeReplacement: goog.html.SafeUrl.INNOCUOUS_STRING,
- typeToUnwrap: goog.html.SafeUrl,
- unwrap: goog.html.SafeUrl.unwrap
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.TRUSTED_RESOURCE_URL] = {
- filter: (
- /**
- * Just returns the safe replacement value because we have no
- * way of declaring that a raw string is a trusted resource so
- * rely on RTTI in all cases.
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string}
- */
- function disallowTrustedResourceUrl(e, a, v) {
- return goog.html.SafeUrl.INNOCUOUS_STRING;
- }),
- safeReplacement: goog.html.SafeUrl.INNOCUOUS_STRING,
- typeToUnwrap: goog.html.TrustedResourceUrl,
- unwrap: goog.html.TrustedResourceUrl.unwrap
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_STYLE] = {
- filter: (
- /**
- * Just returns the safe replacement value since we have no
- * way of testing that a raw string is a safe style.
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string}
- */
- function disallowSafeStyle(e, a, v) {
- return goog.html.SafeStyle.INNOCUOUS_STRING;
- }),
- safeReplacement: goog.html.SafeStyle.INNOCUOUS_STRING,
- typeToUnwrap: goog.html.SafeStyle,
- unwrap: goog.html.SafeStyle.unwrap
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_SCRIPT] = {
- filter: (
- /**
- * Just returns the safe replacement value since we have no
- * way of testing that a raw string is a safe script.
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string}
- */
- function disallowSafeScript(e, a, v) {
- return INNOCUOUS_SCRIPT;
- }),
- safeReplacement: INNOCUOUS_SCRIPT,
- typeToUnwrap: goog.html.SafeScript,
- unwrap: goog.html.SafeScript.unwrap
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.ENUM] = {
- filter: (
- /**
- * Checks that the input is allowed for the given attribute on the
- * given element.
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string} v lowercased if allowed, or the safe replacement
- * otherwise.
- */
- function (e, a, v) {
- var lv = String(v).toLowerCase();
- return security.html.contracts.isEnumValueAllowed(e, a, lv)
- ? lv : INNOCUOUS_STRING;
- }),
- safeReplacement: INNOCUOUS_STRING,
- typeToUnwrap: null,
- unwrap: null
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.COMPILE_TIME_CONSTANT] = {
- filter: (
- /**
- * Just returns the safe replacement value.
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string}
- */
- function disallow(e, a, v) {
- return INNOCUOUS_SCRIPT;
- }),
- safeReplacement: INNOCUOUS_STRING,
- typeToUnwrap: goog.string.Const,
- unwrap: goog.string.Const.unwrap
- };
- VALUE_HANDLERS_[security.html.contracts.AttrType.IDENTIFIER] = {
- filter: (
- /**
- * @param {string} e element name
- * @param {string} a attribute name
- * @param {string} v attribute value
- * @return {string}
- */
- function allowIdentifier(e, a, v) {
- return security.polymer_resin.allowedIdentifierPattern_.test(v)
- ? v
- : INNOCUOUS_STRING;
- }),
- safeReplacement: INNOCUOUS_STRING,
- typeToUnwrap: goog.string.Const,
- unwrap: goog.string.Const.unwrap
- };
-
-
- // This assumes that imports have not already fired.
- if (typeof Polymer !== 'undefined' && Polymer.version) {
- initResin();
+ var safeValue = sanitize(node, name, type, finalValue);
+
+ return (
+ security.polymer_resin.allowUnsafeValues_
+ ? finalValue : safeValue);
+ };
+ Polymer.Base._computeFinalAnnotationValue =
+ computeFinalAnnotationSafeValue;
+ if (Polymer.Base._computeFinalAnnotationValue
+ !== computeFinalAnnotationSafeValue) {
+ // We're in use strict, so assignment should fail-fast, but
+ // this is cheap.
+ throw new Error(
+ 'Cannot replace _computeFinalAnnotationValue. Is Polymer frozen?');
+ }
} else {
- window.addEventListener('HTMLImportsLoaded', initResin);
+ var origSanitize = Polymer.sanitizeDOMValue;
+ var sanitizeDOMValue =
+ function sanitizeDOMValue(value, name, type, node) {
+ var origSanitizedValue = origSanitize
+ ? origSanitize.call(Polymer, value, name, type, node)
+ : value;
+ var safeValue = sanitize(node, name, type, origSanitizedValue);
+
+ return (
+ security.polymer_resin.allowUnsafeValues_
+ ? origSanitizedValue : safeValue);
+ };
+ Polymer.sanitizeDOMValue = sanitizeDOMValue;
+ if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
+ // We're in use strict, so assignment should fail-fast, but
+ // this is cheap.
+ throw new Error(
+ 'Cannot install sanitizeDOMValue. Is Polymer frozen?');
+ }
}
-}());
+};
+
+
+/**
+ * Analogous to goog.html.SafeUrl.INNOCUOUS_STRING but
+ * used for const strings and safe html types that
+ * do not have their own defined.
+ * @const
+ * @private
+ */
+security.polymer_resin.INNOCUOUS_STRING_ = 'zClosurez';
+
+/**
+ * @const
+ * @private
+ */
+security.polymer_resin.INNOCUOUS_SCRIPT_ = ' /*zClosurez*/ ';
+
+/**
+ * @type {!Array.}
+ * @const
+ * @private
+ */
+security.polymer_resin.VALUE_HANDLERS_ = [];
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.NONE] = {
+ // A function that maps values to safe values or that
+ // returns a safe replacement value.
+ filter: null,
+ // A safe value that indicates a problem likely occurred
+ // so an event is worth logging.
+ safeReplacement: null,
+ // Constructor for a subtype of TypedString to unwrap
+ typeToUnwrap: null,
+ // Unwraps values that are instanceof typeToUnwrap
+ unwrap: null
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.SAFE_HTML] = {
+ // Assigning a string to srchtml is the same as
+ // assigning a text node.
+ filter: (
+ /**
+ * Converts plain text to HTML that parses to a text node with
+ * equivalent content.
+ *
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string}
+ */
+ function plainTextToHtml(e, a, v) {
+ return goog.string.htmlEscape(v);
+ }),
+ safeReplacement: null,
+ typeToUnwrap: goog.html.SafeHtml,
+ unwrap: goog.html.SafeHtml.unwrap
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.SAFE_URL] = {
+ filter: (
+ /**
+ * Allows safe URLs through, but rejects unsafe ones.
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string}
+ */
+ function allowSafeUrl(e, a, v) {
+ // TODO: Can we do without creating a SafeUrl instance?
+ return goog.html.SafeUrl.sanitize(v).getTypedStringValue();
+ }),
+ safeReplacement: goog.html.SafeUrl.INNOCUOUS_STRING,
+ typeToUnwrap: goog.html.SafeUrl,
+ unwrap: goog.html.SafeUrl.unwrap
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.TRUSTED_RESOURCE_URL] = {
+ filter: (
+ /**
+ * Just returns the safe replacement value because we have no
+ * way of declaring that a raw string is a trusted resource so
+ * rely on RTTI in all cases.
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string}
+ */
+ function disallowTrustedResourceUrl(e, a, v) {
+ return goog.html.SafeUrl.INNOCUOUS_STRING;
+ }),
+ safeReplacement: goog.html.SafeUrl.INNOCUOUS_STRING,
+ typeToUnwrap: goog.html.TrustedResourceUrl,
+ unwrap: goog.html.TrustedResourceUrl.unwrap
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.SAFE_STYLE] = {
+ filter: (
+ /**
+ * Just returns the safe replacement value since we have no
+ * way of testing that a raw string is a safe style.
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string}
+ */
+ function disallowSafeStyle(e, a, v) {
+ return goog.html.SafeStyle.INNOCUOUS_STRING;
+ }),
+ safeReplacement: goog.html.SafeStyle.INNOCUOUS_STRING,
+ typeToUnwrap: goog.html.SafeStyle,
+ unwrap: goog.html.SafeStyle.unwrap
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.SAFE_SCRIPT] = {
+ filter: (
+ /**
+ * Just returns the safe replacement value since we have no
+ * way of testing that a raw string is a safe script.
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string}
+ */
+ function disallowSafeScript(e, a, v) {
+ return security.polymer_resin.INNOCUOUS_SCRIPT_;
+ }),
+ safeReplacement: security.polymer_resin.INNOCUOUS_SCRIPT_,
+ typeToUnwrap: goog.html.SafeScript,
+ unwrap: goog.html.SafeScript.unwrap
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.ENUM] = {
+ filter: (
+ /**
+ * Checks that the input is allowed for the given attribute on the
+ * given element.
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string} v lowercased if allowed, or the safe replacement
+ * otherwise.
+ */
+ function (e, a, v) {
+ var lv = String(v).toLowerCase();
+ return security.html.contracts.isEnumValueAllowed(e, a, lv)
+ ? lv : security.polymer_resin.INNOCUOUS_STRING_;
+ }),
+ safeReplacement: security.polymer_resin.INNOCUOUS_STRING_,
+ typeToUnwrap: null,
+ unwrap: null
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.COMPILE_TIME_CONSTANT] = {
+ filter: (
+ /**
+ * Just returns the safe replacement value.
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string}
+ */
+ function disallow(e, a, v) {
+ return security.polymer_resin.INNOCUOUS_SCRIPT_;
+ }),
+ safeReplacement: security.polymer_resin.INNOCUOUS_STRING_,
+ typeToUnwrap: goog.string.Const,
+ unwrap: goog.string.Const.unwrap
+};
+security.polymer_resin.VALUE_HANDLERS_[
+ security.html.contracts.AttrType.IDENTIFIER] = {
+ filter: (
+ /**
+ * @param {string} e element name
+ * @param {string} a attribute name
+ * @param {string} v attribute value
+ * @return {string}
+ */
+ function allowIdentifier(e, a, v) {
+ return security.polymer_resin.allowedIdentifierPattern_.test(v)
+ ? v
+ : security.polymer_resin.INNOCUOUS_STRING_;
+ }),
+ safeReplacement: security.polymer_resin.INNOCUOUS_STRING_,
+ typeToUnwrap: goog.string.Const,
+ unwrap: goog.string.Const.unwrap
+};
diff --git a/standalone/polymer-resin-debug.js b/standalone/polymer-resin-debug.js
index 356c296..7debb41 100644
--- a/standalone/polymer-resin-debug.js
+++ b/standalone/polymer-resin-debug.js
@@ -2926,154 +2926,163 @@ security.polymer_resin.classifyElement = function(name, ctor) {
}
return customElementsRegistry && customElementsRegistry.get(name) || security.polymer_resin.docRegisteredElements_[name] === security.polymer_resin.docRegisteredElements_ ? security.polymer_resin.CustomElementClassification.CUSTOM : ctor === HTMLUnknownElement ? security.polymer_resin.CustomElementClassification.LEGACY : ctor === HTMLElement && security.polymer_resin.VALID_CUSTOM_ELEMENT_NAME_REGEX_.test(name) ? security.polymer_resin.CustomElementClassification.CUSTOMIZABLE : security.polymer_resin.CustomElementClassification.BUILTIN;
};
-security.polymer_resin.UNSAFE_passThruDisallowedValues = function(enable) {
+security.polymer_resin.UNSAFE_passThruDisallowedValues_ = function(enable) {
goog.DEBUG && (security.polymer_resin.allowUnsafeValues_ = !0 === enable);
};
-security.polymer_resin.allowIdentifierWithPrefix = function(prefix) {
+security.polymer_resin.allowIdentifierWithPrefix_ = function(prefix) {
security.polymer_resin.allowedIdentifierPattern_ = new RegExp(security.polymer_resin.allowedIdentifierPattern_.source + "|^" + goog.string.regExpEscape(prefix));
};
-security.polymer_resin.setReportHandler = function(reportHandler) {
+security.polymer_resin.setReportHandler_ = function(reportHandler) {
security.polymer_resin.reportHandler_ = reportHandler || null;
};
security.polymer_resin.allowedIdentifierPattern_ = /^$/;
security.polymer_resin.allowUnsafeValues_ = !1;
security.polymer_resin.reportHandler_ = void 0;
-(function() {
- function initResin() {
- function getAttributeValue(name) {
- var value = this.getAttribute(name);
- return !value || /[\[\{]/.test(name) ? null : value;
- }
- function sanitize(node, name, type, value) {
- if (!value) {
- return value;
- }
- var nodeType = node.nodeType;
- if (nodeType !== goog.dom.NodeType.ELEMENT) {
- if (nodeType === goog.dom.NodeType.TEXT) {
- var parentElement = node.parentElement;
- if (parentElement && parentElement.nodeType === goog.dom.NodeType.ELEMENT) {
- var parentElementName = parentElement.localName, allowText = !1;
- switch(security.polymer_resin.classifyElement(parentElementName, parentElement.constructor)) {
- case security.polymer_resin.CustomElementClassification.BUILTIN:
- case security.polymer_resin.CustomElementClassification.LEGACY:
- allowText = security.html.contracts.contentTypeForElement(parentElementName) === security.html.contracts.ElementContentType.SAFE_HTML;
- break;
- case security.polymer_resin.CustomElementClassification.CUSTOMIZABLE:
- case security.polymer_resin.CustomElementClassification.CUSTOM:
- allowText = !0;
- }
- if (allowText) {
- return value && value.implementsGoogStringTypedString ? value.getTypedStringValue() : String(value);
- }
- }
- }
- security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!0, "Failed to sanitize %s %s%s node to value %O", node.parentElement && node.parentElement.nodeName, "#text", "", value);
- return "zClosurez";
- }
- var elementName = node.localName;
- var elementName$jscomp$0 = node.localName;
- if (node.getAttribute("is") || security.polymer_resin.classifyElement(elementName$jscomp$0, node.constructor) !== security.polymer_resin.CustomElementClassification.CUSTOM) {
- var uncustomizedProxy = uncustomizedProxies[elementName$jscomp$0];
- uncustomizedProxy || (uncustomizedProxy = uncustomizedProxies[elementName$jscomp$0] = document.createElement(elementName$jscomp$0));
- var elementProxy = uncustomizedProxy;
- } else {
- elementProxy = VANILLA_HTML_ELEMENT_;
- }
- switch(type) {
- case "attribute":
- if (security.html.namealiases.attrToProperty(name) in elementProxy) {
- break;
- }
- return value;
- case "property":
- if (name in elementProxy) {
- break;
+security.polymer_resin.install = function(opt_config) {
+ function getAttributeValue(name) {
+ var value = this.getAttribute(name);
+ return !value || /[\[\{]/.test(name) ? null : value;
+ }
+ function sanitize(node, name, type, value) {
+ if (!value) {
+ return value;
+ }
+ var nodeType = node.nodeType;
+ if (nodeType !== goog.dom.NodeType.ELEMENT) {
+ if (nodeType === goog.dom.NodeType.TEXT) {
+ var parentElement = node.parentElement;
+ if (parentElement && parentElement.nodeType === goog.dom.NodeType.ELEMENT) {
+ var parentElementName = parentElement.localName, allowText = !1;
+ switch(security.polymer_resin.classifyElement(parentElementName, parentElement.constructor)) {
+ case security.polymer_resin.CustomElementClassification.BUILTIN:
+ case security.polymer_resin.CustomElementClassification.LEGACY:
+ allowText = security.html.contracts.contentTypeForElement(parentElementName) === security.html.contracts.ElementContentType.SAFE_HTML;
+ break;
+ case security.polymer_resin.CustomElementClassification.CUSTOMIZABLE:
+ case security.polymer_resin.CustomElementClassification.CUSTOM:
+ allowText = !0;
}
- var worstCase = security.html.namealiases.specialPropertyNameWorstCase(name);
- if (worstCase && worstCase in elementProxy) {
- break;
+ if (allowText) {
+ return value && value.implementsGoogStringTypedString ? value.getTypedStringValue() : String(value);
}
- return value;
- default:
- throw Error(type + ": " + typeof type);
+ }
}
- var attrName = "attribute" == type ? name.toLowerCase() : security.html.namealiases.propertyToAttr(name), attrType = security.html.contracts.typeOfAttribute(elementName, attrName, goog.bind(getAttributeValue, node)), safeValue = "zClosurez";
- if (null != attrType) {
- var valueHandler = VALUE_HANDLERS_[attrType];
- if (valueHandler.typeToUnwrap && value instanceof valueHandler.typeToUnwrap) {
- return valueHandler.unwrap(value);
+ security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!0, "Failed to sanitize %s %s%s node to value %O", node.parentElement && node.parentElement.nodeName, "#text", "", value);
+ return security.polymer_resin.INNOCUOUS_STRING_;
+ }
+ var elementName = node.localName;
+ var elementName$jscomp$0 = node.localName;
+ if (node.getAttribute("is") || security.polymer_resin.classifyElement(elementName$jscomp$0, node.constructor) !== security.polymer_resin.CustomElementClassification.CUSTOM) {
+ var uncustomizedProxy = uncustomizedProxies[elementName$jscomp$0];
+ uncustomizedProxy || (uncustomizedProxy = uncustomizedProxies[elementName$jscomp$0] = document.createElement(elementName$jscomp$0));
+ var elementProxy = uncustomizedProxy;
+ } else {
+ elementProxy = VANILLA_HTML_ELEMENT_;
+ }
+ switch(type) {
+ case "attribute":
+ if (security.html.namealiases.attrToProperty(name) in elementProxy) {
+ break;
}
- var stringValue = value && value.implementsGoogStringTypedString ? value.getTypedStringValue() : String(value), safeValue = valueHandler.filter ? valueHandler.filter(elementName, attrName, stringValue) : stringValue;
- if (safeValue !== valueHandler.safeReplacement) {
- return safeValue;
+ return value;
+ case "property":
+ if (name in elementProxy) {
+ break;
}
- }
- security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!0, 'Failed to sanitize in %s: <%s %s="%O">', elementName, elementName, attrName, value);
- return safeValue;
+ var worstCase = security.html.namealiases.specialPropertyNameWorstCase(name);
+ if (worstCase && worstCase in elementProxy) {
+ break;
+ }
+ return value;
+ default:
+ throw Error(type + ": " + typeof type);
}
- goog.DEBUG && void 0 === security.polymer_resin.reportHandler_ && "undefined" !== typeof console && (security.polymer_resin.reportHandler_ = function(isViolation, formatString, var_args) {
- for (var consoleArgs = [formatString], i = 2, n = arguments.length; i < n; ++i) {
- consoleArgs[i - 1] = arguments[i];
+ var attrName = "attribute" == type ? name.toLowerCase() : security.html.namealiases.propertyToAttr(name), attrType = security.html.contracts.typeOfAttribute(elementName, attrName, goog.bind(getAttributeValue, node)), safeValue = security.polymer_resin.INNOCUOUS_STRING_;
+ if (null != attrType) {
+ var valueHandler = security.polymer_resin.VALUE_HANDLERS_[attrType];
+ if (valueHandler.typeToUnwrap && value instanceof valueHandler.typeToUnwrap) {
+ return valueHandler.unwrap(value);
}
- isViolation ? console.warn.apply(console, consoleArgs) : console.log.apply(console, consoleArgs);
- });
- security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!1, "initResin");
- var uncustomizedProxies = {}, VANILLA_HTML_ELEMENT_ = document.createElement("polyresinuncustomized");
- if (/^1\./.test(Polymer.version)) {
- security.polymer_resin.hintUsesDeprecatedRegisterElement();
- var origCompute = Polymer.Base._computeFinalAnnotationValue, computeFinalAnnotationSafeValue = function(node, property, value, info) {
- var finalValue = origCompute.call(Polymer.Base, node, property, value, info), type = "property";
- if (info && info.propertyName) {
- var name = info.propertyName;
- } else {
- name = property, type = info && info.kind || "property";
- }
- var safeValue = sanitize(node, name, type, finalValue);
- return security.polymer_resin.allowUnsafeValues_ ? finalValue : safeValue;
- };
- Polymer.Base._computeFinalAnnotationValue = computeFinalAnnotationSafeValue;
- if (Polymer.Base._computeFinalAnnotationValue !== computeFinalAnnotationSafeValue) {
- throw Error("Cannot replace _computeFinalAnnotationValue. Is Polymer frozen?");
+ var stringValue = value && value.implementsGoogStringTypedString ? value.getTypedStringValue() : String(value), safeValue = valueHandler.filter ? valueHandler.filter(elementName, attrName, stringValue) : stringValue;
+ if (safeValue !== valueHandler.safeReplacement) {
+ return safeValue;
}
- } else {
- var origSanitize = Polymer.sanitizeDOMValue, sanitizeDOMValue = function(value, name, type, node) {
- var origSanitizedValue = origSanitize ? origSanitize.call(Polymer, value, name, type, node) : value, safeValue = sanitize(node, name, type, origSanitizedValue);
- return security.polymer_resin.allowUnsafeValues_ ? origSanitizedValue : safeValue;
- };
- Polymer.sanitizeDOMValue = sanitizeDOMValue;
- if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
- throw Error("Cannot install sanitizeDOMValue. Is Polymer frozen?");
+ }
+ security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!0, 'Failed to sanitize in %s: <%s %s="%O">', elementName, elementName, attrName, value);
+ return safeValue;
+ }
+ if (opt_config) {
+ var configUnsafePassThruDisallowedValues = opt_config.UNSAFE_passThruDisallowedValues, configAllowedIdentifierPrefixes = opt_config.allowedIdentifierPrefixes, configReportHandler = opt_config.reportHandler;
+ null != configUnsafePassThruDisallowedValues && security.polymer_resin.UNSAFE_passThruDisallowedValues_(configUnsafePassThruDisallowedValues);
+ if (configAllowedIdentifierPrefixes) {
+ for (var i$jscomp$0 = 0, n$jscomp$0 = configAllowedIdentifierPrefixes.length; i$jscomp$0 < n$jscomp$0; ++i$jscomp$0) {
+ security.polymer_resin.allowIdentifierWithPrefix_(configAllowedIdentifierPrefixes[i$jscomp$0]);
}
}
+ void 0 !== configReportHandler && security.polymer_resin.setReportHandler_(configReportHandler);
}
- var VALUE_HANDLERS_ = [];
- VALUE_HANDLERS_[security.html.contracts.AttrType.NONE] = {filter:null, safeReplacement:null, typeToUnwrap:null, unwrap:null};
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_HTML] = {filter:function(e, a, v) {
- return goog.string.htmlEscape(v);
- }, safeReplacement:null, typeToUnwrap:goog.html.SafeHtml, unwrap:goog.html.SafeHtml.unwrap};
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_URL] = {filter:function(e, a, v) {
- return goog.html.SafeUrl.sanitize(v).getTypedStringValue();
- }, safeReplacement:goog.html.SafeUrl.INNOCUOUS_STRING, typeToUnwrap:goog.html.SafeUrl, unwrap:goog.html.SafeUrl.unwrap};
- VALUE_HANDLERS_[security.html.contracts.AttrType.TRUSTED_RESOURCE_URL] = {filter:function() {
- return goog.html.SafeUrl.INNOCUOUS_STRING;
- }, safeReplacement:goog.html.SafeUrl.INNOCUOUS_STRING, typeToUnwrap:goog.html.TrustedResourceUrl, unwrap:goog.html.TrustedResourceUrl.unwrap};
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_STYLE] = {filter:function() {
- return goog.html.SafeStyle.INNOCUOUS_STRING;
- }, safeReplacement:goog.html.SafeStyle.INNOCUOUS_STRING, typeToUnwrap:goog.html.SafeStyle, unwrap:goog.html.SafeStyle.unwrap};
- VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_SCRIPT] = {filter:function() {
- return "/*zClosurez*/";
- }, safeReplacement:"/*zClosurez*/", typeToUnwrap:goog.html.SafeScript, unwrap:goog.html.SafeScript.unwrap};
- VALUE_HANDLERS_[security.html.contracts.AttrType.ENUM] = {filter:function(e, a, v) {
- var lv = String(v).toLowerCase();
- return security.html.contracts.isEnumValueAllowed(e, a, lv) ? lv : "zClosurez";
- }, safeReplacement:"zClosurez", typeToUnwrap:null, unwrap:null};
- VALUE_HANDLERS_[security.html.contracts.AttrType.COMPILE_TIME_CONSTANT] = {filter:function() {
- return "/*zClosurez*/";
- }, safeReplacement:"zClosurez", typeToUnwrap:goog.string.Const, unwrap:goog.string.Const.unwrap};
- VALUE_HANDLERS_[security.html.contracts.AttrType.IDENTIFIER] = {filter:function(e, a, v) {
- return security.polymer_resin.allowedIdentifierPattern_.test(v) ? v : "zClosurez";
- }, safeReplacement:"zClosurez", typeToUnwrap:goog.string.Const, unwrap:goog.string.Const.unwrap};
- "undefined" !== typeof Polymer && Polymer.version ? initResin() : window.addEventListener("HTMLImportsLoaded", initResin);
-})();
+ goog.DEBUG && void 0 === security.polymer_resin.reportHandler_ && "undefined" !== typeof console && (security.polymer_resin.reportHandler_ = function(isViolation, formatString, var_args) {
+ for (var consoleArgs = [formatString], i = 2, n = arguments.length; i < n; ++i) {
+ consoleArgs[i - 1] = arguments[i];
+ }
+ isViolation ? console.warn.apply(console, consoleArgs) : console.log.apply(console, consoleArgs);
+ });
+ security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!1, "initResin");
+ var uncustomizedProxies = {}, VANILLA_HTML_ELEMENT_ = document.createElement("polyresinuncustomized");
+ if (/^1\./.test(Polymer.version)) {
+ security.polymer_resin.hintUsesDeprecatedRegisterElement();
+ var origCompute = Polymer.Base._computeFinalAnnotationValue, computeFinalAnnotationSafeValue = function(node, property, value, info) {
+ var finalValue = origCompute.call(Polymer.Base, node, property, value, info), type = "property";
+ if (info && info.propertyName) {
+ var name = info.propertyName;
+ } else {
+ name = property, type = info && info.kind || "property";
+ }
+ var safeValue = sanitize(node, name, type, finalValue);
+ return security.polymer_resin.allowUnsafeValues_ ? finalValue : safeValue;
+ };
+ Polymer.Base._computeFinalAnnotationValue = computeFinalAnnotationSafeValue;
+ if (Polymer.Base._computeFinalAnnotationValue !== computeFinalAnnotationSafeValue) {
+ throw Error("Cannot replace _computeFinalAnnotationValue. Is Polymer frozen?");
+ }
+ } else {
+ var origSanitize = Polymer.sanitizeDOMValue, sanitizeDOMValue = function(value, name, type, node) {
+ var origSanitizedValue = origSanitize ? origSanitize.call(Polymer, value, name, type, node) : value, safeValue = sanitize(node, name, type, origSanitizedValue);
+ return security.polymer_resin.allowUnsafeValues_ ? origSanitizedValue : safeValue;
+ };
+ Polymer.sanitizeDOMValue = sanitizeDOMValue;
+ if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
+ throw Error("Cannot install sanitizeDOMValue. Is Polymer frozen?");
+ }
+ }
+};
+security.polymer_resin.INNOCUOUS_STRING_ = "zClosurez";
+security.polymer_resin.INNOCUOUS_SCRIPT_ = " /*zClosurez*/ ";
+security.polymer_resin.VALUE_HANDLERS_ = [];
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.NONE] = {filter:null, safeReplacement:null, typeToUnwrap:null, unwrap:null};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_HTML] = {filter:function(e, a, v) {
+ return goog.string.htmlEscape(v);
+}, safeReplacement:null, typeToUnwrap:goog.html.SafeHtml, unwrap:goog.html.SafeHtml.unwrap};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_URL] = {filter:function(e, a, v) {
+ return goog.html.SafeUrl.sanitize(v).getTypedStringValue();
+}, safeReplacement:goog.html.SafeUrl.INNOCUOUS_STRING, typeToUnwrap:goog.html.SafeUrl, unwrap:goog.html.SafeUrl.unwrap};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.TRUSTED_RESOURCE_URL] = {filter:function() {
+ return goog.html.SafeUrl.INNOCUOUS_STRING;
+}, safeReplacement:goog.html.SafeUrl.INNOCUOUS_STRING, typeToUnwrap:goog.html.TrustedResourceUrl, unwrap:goog.html.TrustedResourceUrl.unwrap};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_STYLE] = {filter:function() {
+ return goog.html.SafeStyle.INNOCUOUS_STRING;
+}, safeReplacement:goog.html.SafeStyle.INNOCUOUS_STRING, typeToUnwrap:goog.html.SafeStyle, unwrap:goog.html.SafeStyle.unwrap};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_SCRIPT] = {filter:function() {
+ return security.polymer_resin.INNOCUOUS_SCRIPT_;
+}, safeReplacement:security.polymer_resin.INNOCUOUS_SCRIPT_, typeToUnwrap:goog.html.SafeScript, unwrap:goog.html.SafeScript.unwrap};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.ENUM] = {filter:function(e, a, v) {
+ var lv = String(v).toLowerCase();
+ return security.html.contracts.isEnumValueAllowed(e, a, lv) ? lv : security.polymer_resin.INNOCUOUS_STRING_;
+}, safeReplacement:security.polymer_resin.INNOCUOUS_STRING_, typeToUnwrap:null, unwrap:null};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.COMPILE_TIME_CONSTANT] = {filter:function() {
+ return security.polymer_resin.INNOCUOUS_SCRIPT_;
+}, safeReplacement:security.polymer_resin.INNOCUOUS_STRING_, typeToUnwrap:goog.string.Const, unwrap:goog.string.Const.unwrap};
+security.polymer_resin.VALUE_HANDLERS_[security.html.contracts.AttrType.IDENTIFIER] = {filter:function(e, a, v) {
+ return security.polymer_resin.allowedIdentifierPattern_.test(v) ? v : security.polymer_resin.INNOCUOUS_STRING_;
+}, safeReplacement:security.polymer_resin.INNOCUOUS_STRING_, typeToUnwrap:goog.string.Const, unwrap:goog.string.Const.unwrap};
diff --git a/standalone/polymer-resin.js b/standalone/polymer-resin.js
index e881355..1209336 100644
--- a/standalone/polymer-resin.js
+++ b/standalone/polymer-resin.js
@@ -1,4 +1,4 @@
-(function(){'use strict';var q=function(a,b,c){return a.call.apply(a.bind,arguments)},r=function(a,b,c){if(!a)throw Error();if(2/g,y=/"/g,aa=/'/g,ba=/\x00/g,ca=/[\x00&<>"']/,da=function(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})};var A=function(){this.f=z};A.prototype.o=!0;A.prototype.l=function(){return""};A.prototype.toString=function(){return"Const{}"};var C=function(a){return a instanceof A&&a.constructor===A&&a.f===z?"":"type_error:Const"},z={};var E=function(){this.f=D};E.prototype.o=!0;var D={};E.prototype.l=function(){return""};var ea=function(a){return a instanceof E&&a.constructor===E&&a.f===D?"":"type_error:SafeScript"};var G=function(){this.f=F};G.prototype.o=!0;var F={};G.prototype.l=function(){return""};var fa=function(a){return a instanceof G&&a.constructor===G&&a.f===F?"":"type_error:SafeStyle"};var I=function(){this.f=H};I.prototype.o=!0;I.prototype.l=function(){return""};var ga=function(a){return a instanceof I&&a.constructor===I&&a.f===H?"":"type_error:TrustedResourceUrl"},H={};var K=function(){this.f="";this.A=J};K.prototype.o=!0;K.prototype.l=function(){return this.f};var ha=function(a){return a instanceof K&&a.constructor===K&&a.A===J?a.f:"type_error:SafeUrl"},ia=/^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i,J={},L=function(a){var b=new K;b.f=a;return b};L("about:blank");var N=function(){this.f=M};N.prototype.o=!0;N.prototype.l=function(){return""};var ja=function(a){return a instanceof N&&a.constructor===N&&a.f===M?"":"type_error:SafeHtml"},M={};/*
+(function(){'use strict';/*
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
@@ -11,17 +11,4 @@
subject to an additional IP rights grant found at
http://polymer.github.io/PATENTS.txt
*/
-var ka={align:1,alt:1,autofocus:1,bgcolor:1,border:1,checked:1,"class":1,color:1,cols:1,colspan:1,dir:8,disabled:1,draggable:1,face:1,"for":10,frameborder:1,height:1,hidden:1,href:4,id:10,ismap:1,label:1,lang:1,loop:1,max:1,maxlength:1,min:1,multiple:1,muted:1,name:10,placeholder:1,preload:1,rel:1,required:1,reversed:1,role:1,rows:1,rowspan:1,selected:1,shape:1,size:1,sizes:1,span:1,spellcheck:1,src:4,start:1,step:1,style:5,summary:1,tabindex:1,target:8,title:1,translate:1,valign:1,value:1,width:1,
-wrap:1},O={a:{href:[{c:3}]},area:{href:[{c:3}]},audio:{src:[{c:3}]},blockquote:{cite:[{c:3}]},button:{formaction:[{c:3}],formmethod:[{c:1}],type:[{c:1}]},command:{type:[{c:1}]},del:{cite:[{c:3}]},form:{action:[{c:3}],method:[{c:1}]},img:{src:[{c:3}]},input:{formaction:[{c:3}],formmethod:[{c:1}],max:[{c:1}],min:[{c:1}],src:[{c:3}],step:[{c:1}],type:[{c:1}]},ins:{cite:[{c:3}]},li:{type:[{c:1}]},link:{href:[{c:3,g:"rel",h:"alternate"},{c:3,g:"rel",h:"author"},{c:3,g:"rel",h:"bookmark"},{c:3,g:"rel",
-h:"canonical"},{c:3,g:"rel",h:"cite"},{c:3,g:"rel",h:"help"},{c:3,g:"rel",h:"icon"},{c:3,g:"rel",h:"license"},{c:3,g:"rel",h:"next"},{c:3,g:"rel",h:"prefetch"},{c:3,g:"rel",h:"prerender"},{c:3,g:"rel",h:"prev"},{c:3,g:"rel",h:"search"},{c:3,g:"rel",h:"subresource"}],media:[{c:1}],type:[{c:1}]},menuitem:{icon:[{c:3}]},ol:{type:[{c:1}]},q:{cite:[{c:3}]},source:{media:[{c:1}],src:[{c:3}]},style:{media:[{c:1}]},video:{poster:[{c:3}],src:[{c:3}]}},P={a:1,abbr:1,address:1,applet:4,area:5,article:1,aside:1,
-audio:1,b:1,base:4,bdi:1,bdo:1,blockquote:1,body:1,br:5,button:1,canvas:1,caption:1,cite:1,code:1,col:5,colgroup:1,command:1,data:1,datalist:1,dd:1,del:1,details:1,dfn:1,dialog:1,div:1,dl:1,dt:1,em:1,embed:4,fieldset:1,figcaption:1,figure:1,font:1,footer:1,form:1,frame:1,frameset:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,head:1,header:1,hr:5,html:1,i:1,iframe:4,img:5,input:5,ins:1,kbd:1,keygen:5,label:1,legend:1,li:1,link:5,main:1,map:1,mark:1,math:4,menu:1,menuitem:1,meta:4,meter:1,nav:1,noscript:1,object:4,
-ol:1,optgroup:1,option:1,output:1,p:1,param:5,picture:1,pre:1,progress:1,q:1,rb:1,rp:1,rt:1,rtc:1,ruby:1,s:1,samp:1,script:3,section:1,select:1,slot:1,small:1,source:5,span:1,strong:1,style:2,sub:1,summary:1,sup:1,svg:4,table:1,tbody:1,td:1,template:4,textarea:1,tfoot:1,th:1,thead:1,time:1,title:1,tr:1,track:5,u:1,ul:1,"var":1,video:1,wbr:5},la=[{auto:!0,ltr:!0,rtl:!0},{_self:!0,_blank:!0}],Q={"*":{dir:0,target:1}};var U=function(){if(!R){var a=ma,b={};for(c in a)b[c]=a[c];R=b;a=0;for(b=S.length;a")&&(a=a.replace(w,">")),-1!=a.indexOf('"')&&(a=a.replace(y,""")),-1!=a.indexOf("'")&&(a=a.replace(aa,"'")),-1!=a.indexOf("\x00")&&(a=a.replace(ba,"")));return a},v:null,j:N,m:ja};b[3]={filter:function(a,b,g){a=g;a instanceof K||(a=a.o?a.l():String(a),ia.test(a)||(a="about:invalid#zClosurez"),
-a=L(a));return a.l()},v:"about:invalid#zClosurez",j:K,m:ha};b[4]={filter:function(){return"about:invalid#zClosurez"},v:"about:invalid#zClosurez",j:I,m:ga};b[5]={filter:function(){return"zClosurez"},v:"zClosurez",j:G,m:fa};b[7]={filter:function(){return"/*zClosurez*/"},v:"/*zClosurez*/",j:E,m:ea};b[8]={filter:function(a,b,g){g=String(g).toLowerCase();a:{var c=null;(a=Q[a])&&(c=a[b]);if("number"!=typeof c&&((a=Q["*"])&&(c=a[b]),"number"!=typeof c)){b=!1;break a}b=!0===la[c][String(g).toLowerCase()]}return b?
-g:"zClosurez"},v:"zClosurez",j:null,m:null};b[9]={filter:function(){return"/*zClosurez*/"},v:"zClosurez",j:A,m:C};b[10]={filter:function(a,b,g){return oa.test(g)?g:"zClosurez"},v:"zClosurez",j:A,m:C};"undefined"!==typeof Polymer&&Polymer.version?a():window.addEventListener("HTMLImportsLoaded",a)})();}());
+}());
diff --git a/text-node-test.html b/text-node-test.html
index 65faa8e..19efab5 100644
--- a/text-node-test.html
+++ b/text-node-test.html
@@ -20,6 +20,7 @@
+
Text Node Tests