diff --git a/gradle/build.gradle b/gradle/build.gradle index 798f9cd92..79fb31007 100644 --- a/gradle/build.gradle +++ b/gradle/build.gradle @@ -26,6 +26,7 @@ plugins { id 'com.bmuschko.docker-java-application' version '4.0.4' apply false id 'net.ltgt.errorprone' version '0.6.1' id 'checkstyle' + id "com.moowork.node" version "1.2.0" } apply from: 'dependencies.gradle' @@ -211,6 +212,32 @@ subprojects { } } + if (project.path.contains("default")) { + def coreResourcesDir = "${rootDir}/core/build/resources/main" + war { + from("${coreResourcesDir}/google/registry/ui") { + include "registrar_bin*" + into("assets/js") + rename { String filename -> filename.replace("bin", "dbg")} + } + from("${coreResourcesDir}/google/registry/ui") { + include "registrar_bin*" + into("assets/js") + } + from("${coreResourcesDir}/google/registry/ui/css") { + include "registrar*" + into("assets/css") + } + from("${coreResourcesDir}/google/registry/ui/assets/images") { + include "**/*" + into("assets/images") + } + from("${coreResourcesDir}/google/registry/ui/html") { + include "*.html" + } + } + } + appengine { deploy { // TODO: change this to a variable. diff --git a/gradle/core/build.gradle b/gradle/core/build.gradle index 8947ad94c..b3effcfc5 100644 --- a/gradle/core/build.gradle +++ b/gradle/core/build.gradle @@ -5,6 +5,7 @@ plugins { // Path to code generated by ad hoc tasks in this project. A separate path is // used for easy inspection. def generatedDir = "${project.buildDir}/generated/source/custom/main" +def resourcesDir = "${project.buildDir}/resources/main" // Tests that conflict with (mostly unidentified) members of the main test // suite. It is unclear if they are offenders (i.e., those that pollute global @@ -59,6 +60,7 @@ configurations { // either compile or testRuntime. However, they may be needed at runtime. // TODO(weiminyu): identify runtime dependencies and remove the rest. maybeRuntime + closureCompiler } // Known issues: @@ -269,6 +271,8 @@ dependencies { // Tool dependencies. used for doc generation. compile files("${System.properties['java.home']}/../lib/tools.jar") + + closureCompiler deps['com.google.javascript:closure-compiler'] } task jaxbToJava { @@ -384,9 +388,43 @@ task soyToJava { } } +task soyToJS { + ext.soyToJS = { outputDirectory, soyFiles , deps-> + javaexec { + main = "com.google.template.soy.SoyToJsSrcCompiler" + classpath configurations.soy + + args "--outputPathFormat", "${outputDirectory}/{INPUT_FILE_NAME}.js", + "--allowExternalCalls", "false", + "--srcs", "${soyFiles.join(',')}", + "--shouldProvideRequireSoyNamespaces", "true", + "--compileTimeGlobalsFile", "${javaDir}/google/registry/ui/globals.txt", + "--deps", "${deps.join(',')}" + } + } + + doLast { + def rootSoyFiles = + fileTree( + dir: "${javaDir}/google/registry/ui/soy", + include: ['*.soy']) + + soyToJS("${generatedDir}/google/registry/ui/soy", rootSoyFiles, "") + soyToJS("${generatedDir}/google/registry/ui/soy/registrar", + files { + file("${javaDir}/google/registry/ui/soy/registrar").listFiles() + }.filter { + it.name.endsWith(".soy") + }.filter{ + // TODO(b/123653579): add this back in when it compiles + !it.name.equals("OteSetupConsole.soy") + }, rootSoyFiles) + } +} + task stylesheetsToJavascript { def cssSourceDir = "${javaDir}/google/registry/ui/css" - def outputDir = "${project.buildDir}/resources/main/google/registry/ui/css" + def outputDir = "${resourcesDir}/google/registry/ui/css" inputs.dir cssSourceDir outputs.dir outputDir @@ -416,23 +454,40 @@ task stylesheetsToJavascript { doLast { file("${outputDir}").mkdirs() - def srcFiles = [ - "${cssSourceDir}/console.css", - "${cssSourceDir}/contact-settings.css", - "${cssSourceDir}/contact-us.css", - "${cssSourceDir}/dashboard.css", - "${cssSourceDir}/epp.css", - "${cssSourceDir}/forms.css", - "${cssSourceDir}/kd_components.css", - "${cssSourceDir}/registry.css", - "${cssSourceDir}/resources.css", - "${cssSourceDir}/security-settings.css" - ] - cssCompile("${outputDir}/registrar_bin", false, srcFiles) - cssCompile("${outputDir}/registrar_dbg", true, srcFiles) + def ignoredFiles = ["demo_css.css", "registrar_imports_raw.css"] + def sourceFiles = [] + // include all CSS files that we find except for the ones explicitly ignored + fileTree(cssSourceDir).each { + if (it.name.endsWith(".css") && !ignoredFiles.contains(it.name)) { + sourceFiles << (cssSourceDir + "/" + it.name) + } + } + cssCompile("${outputDir}/registrar_bin", false, sourceFiles) + cssCompile("${outputDir}/registrar_dbg", true, sourceFiles) } } +task compileProdJS(type: JavaExec) { + // TODO(b/123647609) clean this up in general + // - have two separate tasks for dev/prod + // - the prod task uses the ADVANCED_OPTIMIZATION flag. + // - have this be a configurable task so we can change the inputs/outputs + classpath configurations.closureCompiler + main = 'com.google.javascript.jscomp.CommandLineRunner' + + def closureArgs = [] + closureArgs << "--compilation_level=SIMPLE_OPTIMIZATIONS" + closureArgs << "--js_output_file=${resourcesDir}/google/registry/ui/registrar_bin.js" + closureArgs << "--js=${rootDir}/node_modules/google-closure-library/**.js" + closureArgs << "--js=${rootDir}/node_modules/soyutils_usegoog.js" + closureArgs << "--entry_point=goog:registry.registrar.main" + closureArgs << "--externs=${javaDir}/google/registry/ui/externs/json.js" + closureArgs << "--js=${resourcesDir}/google/registry/ui/css/registrar_bin.css.js" + closureArgs << "--js=${javaDir}/google/registry/ui/js/**.js" + closureArgs << "--js=${generatedDir}/google/registry/ui/soy/**.js" + args closureArgs +} + compileJava.dependsOn jaxbToJava compileJava.dependsOn soyToJava @@ -440,6 +495,12 @@ compileJava.dependsOn soyToJava // resources folder before copying data into it. stylesheetsToJavascript.dependsOn processResources classes.dependsOn stylesheetsToJavascript +compileProdJS.dependsOn stylesheetsToJavascript +compileProdJS.dependsOn rootProject.npmInstall +compileProdJS.dependsOn processResources +compileProdJS.dependsOn processTestResources +compileProdJS.dependsOn soyToJS +assemble.dependsOn compileProdJS // Make testing artifacts available to be depended up on by other projects. // TODO: factor out google.registry.testing to be a separate project. diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 36bcf1c2d..949a2160b 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -57,6 +57,7 @@ ext { 'com.google.http-client:google-http-client:1.28.0', 'com.google.http-client:google-http-client-appengine:1.22.0', 'com.google.http-client:google-http-client-jackson2:1.25.0', + 'com.google.javascript:closure-compiler:v20190121', 'com.google.monitoring-client:contrib:1.0.4', 'com.google.monitoring-client:metrics:1.0.4', 'com.google.monitoring-client:stackdriver:1.0.4', diff --git a/gradle/node_modules/soyutils_usegoog.js b/gradle/node_modules/soyutils_usegoog.js new file mode 100644 index 000000000..a5b40ac7d --- /dev/null +++ b/gradle/node_modules/soyutils_usegoog.js @@ -0,0 +1,2457 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview + * + * NOTE (gbrodman) this file is taken from the open source version located at + * https://github.com/google/closure-templates/blob/63f34802b543dadb72a078b006a0e7e4dbc71d7e/javascript/soyutils_usegoog.js + * and slightly modified so that it compiles. + * + * Utility functions and classes for Soy gencode + * + *
+ * This file contains utilities that should only be called by Soy-generated + * JS code. Please do not use these functions directly from + * your hand-written code. Their names all start with '$$', or exist within the + * soydata.VERY_UNSAFE namespace. + * + *
TODO(lukes): ensure that the above pattern is actually followed + * consistently. + * + */ +goog.provide('soy'); +goog.provide('soy.asserts'); +goog.provide('soy.esc'); +goog.provide('soydata'); +goog.provide('soydata.SanitizedHtml'); +goog.provide('soydata.VERY_UNSAFE'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.debug'); +goog.require('goog.format'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.html.uncheckedconversions'); +goog.require('goog.i18n.BidiFormatter'); +goog.require('goog.i18n.bidi'); +goog.require('goog.object'); +goog.require('goog.soy.data.SanitizedContent'); +goog.require('goog.soy.data.SanitizedContentKind'); +goog.require('goog.soy.data.SanitizedCss'); +goog.require('goog.soy.data.SanitizedHtml'); +goog.require('goog.soy.data.SanitizedHtmlAttribute'); +goog.require('goog.soy.data.SanitizedJs'); +goog.require('goog.soy.data.SanitizedTrustedResourceUri'); +goog.require('goog.soy.data.SanitizedUri'); +goog.require('goog.soy.data.UnsanitizedText'); +goog.require('goog.string'); +goog.require('goog.string.Const'); + +// ----------------------------------------------------------------------------- +// soydata: Defines typed strings, e.g. an HTML string {@code "ac"} is +// semantically distinct from the plain text string {@code "ac"} and smart +// templates can take that distinction into account. + +/** + * Checks whether a given value is of a given content kind. + * + * @param {*} value The value to be examined. + * @param {goog.soy.data.SanitizedContentKind} contentKind The desired content + * kind. + * @return {boolean} Whether the given value is of the given kind. + * @private + */ +soydata.isContentKind_ = function(value, contentKind) { + // TODO(user): This function should really include the assert on + // value.constructor that is currently sprinkled at most of the call sites. + // Unfortunately, that would require a (debug-mode-only) switch statement. + // TODO(user): Perhaps we should get rid of the contentKind property + // altogether and only at the constructor. + return value != null && value.contentKind === contentKind; +}; + + +/** + * Returns a given value's contentDir property, constrained to a + * goog.i18n.bidi.Dir value or null. Returns null if the value is null, + * undefined, a primitive or does not have a contentDir property, or the + * property's value is not 1 (for LTR), -1 (for RTL), or 0 (for neutral). + * + * @param {*} value The value whose contentDir property, if any, is to + * be returned. + * @return {?goog.i18n.bidi.Dir} The contentDir property. + */ +soydata.getContentDir = function(value) { + if (value != null) { + switch (value.contentDir) { + case goog.i18n.bidi.Dir.LTR: + return goog.i18n.bidi.Dir.LTR; + case goog.i18n.bidi.Dir.RTL: + return goog.i18n.bidi.Dir.RTL; + case goog.i18n.bidi.Dir.NEUTRAL: + return goog.i18n.bidi.Dir.NEUTRAL; + } + } + return null; +}; + + +/** + * This class is only a holder for `soydata.SanitizedHtml.from`. Do not + * instantiate or extend it. Use `goog.soy.data.SanitizedHtml` instead. + * + * @constructor + * @extends {goog.soy.data.SanitizedHtml} + * @abstract + */ +soydata.SanitizedHtml = function() { + soydata.SanitizedHtml.base(this, 'constructor'); // Throws an exception. +}; +goog.inherits(soydata.SanitizedHtml, goog.soy.data.SanitizedHtml); + +/** + * Returns a SanitizedHtml object for a particular value. The content direction + * is preserved. + * + * This HTML-escapes the value unless it is already SanitizedHtml or SafeHtml. + * + * @param {*} value The value to convert. If it is already a SanitizedHtml + * object, it is left alone. + * @return {!goog.soy.data.SanitizedHtml} A SanitizedHtml object derived from + * the stringified value. It is escaped unless the input is SanitizedHtml or + * SafeHtml. + */ +soydata.SanitizedHtml.from = function(value) { + // The check is soydata.isContentKind_() inlined for performance. + if (value != null && + value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml); + return /** @type {!goog.soy.data.SanitizedHtml} */ (value); + } + if (value instanceof goog.html.SafeHtml) { + return soydata.VERY_UNSAFE.ordainSanitizedHtml( + goog.html.SafeHtml.unwrap(value), value.getDirection()); + } + return soydata.VERY_UNSAFE.ordainSanitizedHtml( + soy.esc.$$escapeHtmlHelper(String(value)), soydata.getContentDir(value)); +}; + + +/** + * Empty string, used as a type in Soy templates. + * @enum {string} + * @private + */ +soydata.$$EMPTY_STRING_ = { + VALUE: '' +}; + + +/** + * Creates a factory for SanitizedContent types. + * + * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can + * instantiate Sanitized* classes, without making the Sanitized* constructors + * publicly usable. Requiring all construction to use the VERY_UNSAFE names + * helps callers and their reviewers easily tell that creating SanitizedContent + * is not always safe and calls for careful review. + * + * @param {function(new: T)} ctor A constructor. + * @return {!function(*, ?goog.i18n.bidi.Dir=): T} A factory that takes + * content and an optional content direction and returns a new instance. If + * the content direction is undefined, ctor.prototype.contentDir is used. + * @template T + * @private + */ +soydata.$$makeSanitizedContentFactory_ = function(ctor) { + /** + * @param {string} content + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ + function InstantiableCtor(content) { + /** @override */ + this.content = content; + } + InstantiableCtor.prototype = ctor.prototype; + /** + * Creates a ctor-type SanitizedContent instance. + * + * @param {*} content The content to put in the instance. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction. If + * undefined, ctor.prototype.contentDir is used. + * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually + * of type T above (ctor's type, a descendant of SanitizedContent), but + * there is no way to express that here. + */ + function sanitizedContentFactory(content, opt_contentDir) { + var result = new InstantiableCtor(String(content)); + if (opt_contentDir !== undefined) { + result.contentDir = opt_contentDir; + } + return result; + } + return sanitizedContentFactory; +}; + + +/** + * Creates a factory for SanitizedContent types that should always have their + * default directionality. + * + * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can + * instantiate Sanitized* classes, without making the Sanitized* constructors + * publicly usable. Requiring all construction to use the VERY_UNSAFE names + * helps callers and their reviewers easily tell that creating SanitizedContent + * is not always safe and calls for careful review. + * + * @param {function(new: T, string)} ctor A constructor. + * @return {!function(*): T} A factory that takes content and returns a new + * instance (with default directionality, i.e. ctor.prototype.contentDir). + * @template T + * @private + */ +soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_ = function(ctor) { + /** + * @param {string} content + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ + function InstantiableCtor(content) { + /** @override */ + this.content = content; + } + InstantiableCtor.prototype = ctor.prototype; + /** + * Creates a ctor-type SanitizedContent instance. + * + * @param {*} content The content to put in the instance. + * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually + * of type T above (ctor's type, a descendant of SanitizedContent), but + * there is no way to express that here. + */ + function sanitizedContentFactory(content) { + var result = new InstantiableCtor(String(content)); + return result; + } + return sanitizedContentFactory; +}; + + +// ----------------------------------------------------------------------------- +// Sanitized content ordainers. Please use these with extreme caution (with the +// exception of markUnsanitizedText). A good recommendation is to limit usage +// of these to just a handful of files in your source tree where usages can be +// carefully audited. + + +/** + * Protects a string from being used in an noAutoescaped context. + * + * This is useful for content where there is significant risk of accidental + * unescaped usage in a Soy template. A great case is for user-controlled + * data that has historically been a source of vulernabilities. + * + * @param {*} content Text to protect. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if + * unknown and thus to be estimated when necessary. Default: null. + * @return {!goog.soy.data.UnsanitizedText} A wrapper that is rejected by the + * Soy noAutoescape print directive. + */ +soydata.markUnsanitizedText = function(content, opt_contentDir) { + return new goog.soy.data.UnsanitizedText(content, opt_contentDir); +}; + + +/** + * Takes a leap of faith that the provided content is "safe" HTML. + * + * @param {*} content A string of HTML that can safely be embedded in + * a PCDATA context in your app. If you would be surprised to find that an + * HTML sanitizer produced `s` (e.g. it runs code or fetches bad URLs) + * and you wouldn't write a template that produces `s` on security or + * privacy grounds, then don't pass `s` here. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if + * unknown and thus to be estimated when necessary. Default: null. + * @return {!goog.soy.data.SanitizedHtml} Sanitized content wrapper that + * indicates to Soy not to escape when printed as HTML. + */ +soydata.VERY_UNSAFE.ordainSanitizedHtml = + soydata.$$makeSanitizedContentFactory_(goog.soy.data.SanitizedHtml); + + +/** + * Takes a leap of faith that the provided content is "safe" (non-attacker- + * controlled, XSS-free) Javascript. + * + * @param {*} content Javascript source that when evaluated does not + * execute any attacker-controlled scripts. + * @return {!goog.soy.data.SanitizedJs} Sanitized content wrapper that indicates + * to Soy not to escape when printed as Javascript source. + */ +soydata.VERY_UNSAFE.ordainSanitizedJs = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedJs); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as a URI + * in a Soy template. + * + * This creates a Soy SanitizedContent object which indicates to Soy there is + * no need to escape it when printed as a URI (e.g. in an href or src + * attribute), such as if it's already been encoded or if it's a Javascript: + * URI. + * + * @param {*} content A chunk of URI that the caller knows is safe to + * emit in a template. + * @return {!goog.soy.data.SanitizedUri} Sanitized content wrapper that + * indicates to Soy not to escape or filter when printed in URI context. + */ +soydata.VERY_UNSAFE.ordainSanitizedUri = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedUri); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as a + * TrustedResourceUri in a Soy template. + * + * This creates a Soy SanitizedContent object which indicates to Soy there is + * no need to filter it when printed as a TrustedResourceUri. + * + * @param {*} content A chunk of TrustedResourceUri such as that the caller + * knows is safe to emit in a template. + * @return {!goog.soy.data.SanitizedTrustedResourceUri} Sanitized content + * wrapper that indicates to Soy not to escape or filter when printed in + * TrustedResourceUri context. + */ +soydata.VERY_UNSAFE.ordainSanitizedTrustedResourceUri = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedTrustedResourceUri); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as an + * HTML attribute. + * + * @param {*} content An attribute name and value, such as + * {@code dir="ltr"}. + * @return {!goog.soy.data.SanitizedHtmlAttribute} Sanitized content wrapper + * that indicates to Soy not to escape when printed as an HTML attribute. + */ +soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedHtmlAttribute); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as CSS + * in a style block. + * + * @param {*} content CSS, such as `color:#c3d9ff`. + * @return {!goog.soy.data.SanitizedCss} Sanitized CSS wrapper that indicates to + * Soy there is no need to escape or filter when printed in CSS context. + */ +soydata.VERY_UNSAFE.ordainSanitizedCss = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedCss); + + +// ----------------------------------------------------------------------------- +// Soy-generated utilities in the soy namespace. Contains implementations for +// common soyfunctions (e.g. keys()) and escaping/print directives. + + +/** + * Whether the locale is right-to-left. + * + * @type {boolean} + */ +soy.$$IS_LOCALE_RTL = goog.i18n.bidi.IS_RTL; + + +/** + * Builds an augmented map. The returned map will contain mappings from both + * the base map and the additional map. If the same key appears in both, then + * the value from the additional map will be visible, while the value from the + * base map will be hidden. The base map will be used, but not modified. + * + * @param {!Object} baseMap The original map to augment. + * @param {!Object} additionalMap A map containing the additional mappings. + * @return {!Object} An augmented map containing both the original and + * additional mappings. + */ +soy.$$augmentMap = function(baseMap, additionalMap) { + return soy.$$assignDefaults(soy.$$assignDefaults({}, additionalMap), baseMap); +}; + + +/** + * Copies extra properties into an object if they do not already exist. The + * destination object is mutated in the process. + * + * @param {!Object} obj The destination object to update. + * @param {!Object} defaults An object with default properties to apply. + * @return {!Object} The destination object for convenience. + */ +soy.$$assignDefaults = function(obj, defaults) { + for (var key in defaults) { + if (!(key in obj)) { + obj[key] = defaults[key]; + } + } + + return obj; +}; + + +/** + * Checks that the given map key is a string. + * + *
This is used to validate keys for legacy object map literals.
+ *
+ * @param {*} key Key to check.
+ * @return {string} The given key.
+ */
+soy.$$checkLegacyObjectMapLiteralKey = function(key) {
+ if ((typeof key) != 'string') {
+ throw Error(
+ 'Map literal\'s key expression must evaluate to string' +
+ ' (encountered type "' + (typeof key) + '").');
+ }
+ return key;
+};
+
+
+/**
+ * Gets the keys in a map as an array. There are no guarantees on the order.
+ * @param {Object} map The map to get the keys of.
+ * @return {!Array Important: This function must always be called with a string constant.
+ *
+ * If Closure Compiler is not being used, then this is just this identity
+ * function. If Closure Compiler is being used, then each call to this function
+ * will be replaced with a short string constant, which will be consistent per
+ * input name.
+ *
+ * @param {string} delTemplateName The delegate template name for which to get a
+ * consistent unique id.
+ * @return {string} A unique id that is consistent per input name.
+ *
+ * @idGenerator {consistent}
+ */
+soy.$$getDelTemplateId = function(delTemplateName) {
+ return delTemplateName;
+};
+
+
+/**
+ * Map from registered delegate template key to the priority of the
+ * implementation.
+ * @type {Object}
+ * @private
+ */
+soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {};
+
+/**
+ * Map from registered delegate template key to the implementation function.
+ * @type {Object}
+ * @private
+ */
+soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {};
+
+
+/**
+ * Registers a delegate implementation. If the same delegate template key (id
+ * and variant) has been registered previously, then priority values are
+ * compared and only the higher priority implementation is stored (if
+ * priorities are equal, an error is thrown).
+ *
+ * @param {string} delTemplateId The delegate template id.
+ * @param {string} delTemplateVariant The delegate template variant (can be
+ * empty string).
+ * @param {number} delPriority The implementation's priority value.
+ * @param {Function} delFn The implementation function.
+ */
+soy.$$registerDelegateFn = function(
+ delTemplateId, delTemplateVariant, delPriority, delFn) {
+ var mapKey = 'key_' + delTemplateId + ':' + delTemplateVariant;
+ var currPriority = soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey];
+ if (currPriority === undefined || delPriority > currPriority) {
+ // Registering new or higher-priority function: replace registry entry.
+ soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey] = delPriority;
+ soy.$$DELEGATE_REGISTRY_FUNCTIONS_[mapKey] = delFn;
+ } else if (delPriority == currPriority) {
+ // Registering same-priority function: error.
+ throw Error(
+ 'Encountered two active delegates with the same priority ("' +
+ delTemplateId + ':' + delTemplateVariant + '").');
+ } else {
+ // Registering lower-priority function: do nothing.
+ }
+};
+
+
+/**
+ * Retrieves the (highest-priority) implementation that has been registered for
+ * a given delegate template key (id and variant). If no implementation has
+ * been registered for the key, then the fallback is the same id with empty
+ * variant. If the fallback is also not registered, and allowsEmptyDefault is
+ * true, then returns an implementation that is equivalent to an empty template
+ * (i.e. rendered output would be empty string).
+ *
+ * @param {string} delTemplateId The delegate template id.
+ * @param {string} delTemplateVariant The delegate template variant (can be
+ * empty string).
+ * @param {boolean} allowsEmptyDefault Whether to default to the empty template
+ * function if there's no active implementation.
+ * @return {Function} The retrieved implementation function.
+ */
+soy.$$getDelegateFn = function(
+ delTemplateId, delTemplateVariant, allowsEmptyDefault) {
+ var delFn =
+ soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':' + delTemplateVariant];
+ if (!delFn && delTemplateVariant != '') {
+ // Fallback to empty variant.
+ delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':'];
+ }
+
+ if (delFn) {
+ return delFn;
+ } else if (allowsEmptyDefault) {
+ return soy.$$EMPTY_TEMPLATE_FN_;
+ } else {
+ throw Error(
+ 'Found no active impl for delegate call to "' + delTemplateId +
+ (delTemplateVariant ? ':' + delTemplateVariant : '') +
+ '" (and delcall does not set allowemptydefault="true").');
+ }
+};
+
+
+/**
+ * Private helper soy.$$getDelegateFn(). This is the empty template function
+ * that is returned whenever there's no delegate implementation found.
+ *
+ * @param {Object
+ * Escapes HTML special characters so that the value will not prematurely end
+ * the body of a tag like {@code }.
+ *
+ * Will normalize known safe HTML to make sure that sanitized HTML (which could
+ * contain an innocuous {@code } don't prematurely end an RCDATA
+ * element.
+ *
+ * @param {?} value The string-like value to be escaped. May not be a string,
+ * but the value will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeHtmlRcdata = function(value) {
+ if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) {
+ goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml);
+ return soy.esc.$$normalizeHtmlHelper(value.getContent());
+ }
+ return soy.esc.$$escapeHtmlHelper(value);
+};
+
+
+/**
+ * Matches any/only HTML5 void elements' start tags.
+ * See http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
+ * @type {RegExp}
+ * @private
+ */
+soy.$$HTML5_VOID_ELEMENTS_ = new RegExp(
+ '^<(?:area|base|br|col|command|embed|hr|img|input' +
+ '|keygen|link|meta|param|source|track|wbr)\\b');
+
+
+/**
+ * Removes HTML tags from a string of known safe HTML.
+ * If opt_tagWhitelist is not specified or is empty, then
+ * the result can be used as an attribute value.
+ *
+ * @param {*} value The HTML to be escaped. May not be a string, but the
+ * value will be coerced to a string.
+ * @param {Object and
from
+ // breaking the layout of containing HTML.
+ return html + finalCloseTags;
+};
+
+
+/**
+ * Make sure that tag boundaries are not broken by Safe CSS when embedded in a
+ * {@code