Bug 1250104 - Import the "jsesc" library for escaping JavaScript strings. r=rnewman
authorAlexis Métaireau <alexis@notmyidea.org>
Wed, 24 Feb 2016 15:40:55 +0100
changeset 290865 089d27ec5d7c0dfeb1b553a39db431e393000f77
parent 290864 346ac4ebf25034b577080c0733275334d3a07fd0
child 290866 4772ee550675da7ea644d8cf41ca0021caf18c65
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1250104
milestone48.0a1
Bug 1250104 - Import the "jsesc" library for escaping JavaScript strings. r=rnewman
.eslintignore
toolkit/modules/moz.build
toolkit/modules/tests/xpcshell/test_jsesc.js
toolkit/modules/tests/xpcshell/xpcshell.ini
toolkit/modules/third_party/jsesc/README
toolkit/modules/third_party/jsesc/fx-header
toolkit/modules/third_party/jsesc/jsesc.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -204,8 +204,11 @@ toolkit/components/search/nsSearchServic
 toolkit/components/url-classifier/**
 toolkit/components/urlformatter/nsURLFormatter.js
 toolkit/identity/FirefoxAccounts.jsm
 toolkit/modules/AppConstants.jsm
 toolkit/mozapps/downloads/nsHelperAppDlg.js
 toolkit/mozapps/extensions/internal/AddonConstants.jsm
 toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js
 toolkit/webapps/**
+
+# Third party
+toolkit/modules/third_party/**
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -82,16 +82,17 @@ EXTRA_JS_MODULES += [
     'Task.jsm',
     'Timer.jsm',
     'Troubleshoot.jsm',
     'UpdateUtils.jsm',
     'WebChannel.jsm',
     'WindowDraggingUtils.jsm',
     'ZipUtils.jsm',
 ]
+EXTRA_JS_MODULES.third_party.jsesc += ['third_party/jsesc/jsesc.js']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
     DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
     DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
 
 EXTRA_PP_JS_MODULES += [
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_jsesc.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/third_party/jsesc/jsesc.js");
+
+function run_test() {
+  do_check_eq(jsesc("teééést", {lowercaseHex: true}), "te\\xe9\\xe9\\xe9st");
+  do_check_eq(jsesc("teééést", {lowercaseHex: false}), "te\\xE9\\xE9\\xE9st");
+}
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -16,16 +16,18 @@ skip-if = toolkit == 'android'
 [test_DeferredTask.js]
 skip-if = toolkit == 'android'
 [test_FileUtils.js]
 skip-if = toolkit == 'android'
 [test_GMPInstallManager.js]
 skip-if = toolkit == 'android'
 [test_Http.js]
 skip-if = toolkit == 'android'
+[test_jsesc.js]
+skip-if = toolkit == 'android'
 [test_Log.js]
 skip-if = toolkit == 'android'
 [test_MatchPattern.js]
 skip-if = toolkit == 'android'
 [test_MatchGlobs.js]
 skip-if = toolkit == 'android'
 [test_NewTabUtils.js]
 skip-if = toolkit == 'android'
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/third_party/jsesc/README
@@ -0,0 +1,10 @@
+This code comes from an externally managed library, available at
+<https://github.com/mathiasbynens/jsesc>. Bugs should be reported directly
+upstream and integrated back here.
+
+In order to regenerate this file, you need to do the following:
+
+  $ git clone git@github.com:mathiasbynens/jsesc.git && cd jsesc
+  $ grunt template
+  $ export MOZ_JSESC="../mozilla-central/toolkit/modules/third_party/jsesc"
+  $ cat $MOZ_JSESC/fx-header jsesc.js > $MOZ_JSESC/jsesc.js
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/third_party/jsesc/fx-header
@@ -0,0 +1,26 @@
+/*
+DO NOT TOUCH THIS FILE DIRECTLY. See the README for instructions.
+
+Copyright Mathias Bynens <https://mathiasbynens.be/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+this.EXPORTED_SYMBOLS = ["jsesc"];
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/third_party/jsesc/jsesc.js
@@ -0,0 +1,299 @@
+/*
+DO NOT TOUCH THIS FILE DIRECTLY. See the README for instructions.
+
+Copyright Mathias Bynens <https://mathiasbynens.be/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+this.EXPORTED_SYMBOLS = ["jsesc"];
+/*! https://mths.be/jsesc v1.0.0 by @mathias */
+;(function(root) {
+
+	// Detect free variables `exports`
+	var freeExports = typeof exports == 'object' && exports;
+
+	// Detect free variable `module`
+	var freeModule = typeof module == 'object' && module &&
+		module.exports == freeExports && module;
+
+	// Detect free variable `global`, from Node.js or Browserified code,
+	// and use it as `root`
+	var freeGlobal = typeof global == 'object' && global;
+	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+		root = freeGlobal;
+	}
+
+	/*--------------------------------------------------------------------------*/
+
+	var object = {};
+	var hasOwnProperty = object.hasOwnProperty;
+	var forOwn = function(object, callback) {
+		var key;
+		for (key in object) {
+			if (hasOwnProperty.call(object, key)) {
+				callback(key, object[key]);
+			}
+		}
+	};
+
+	var extend = function(destination, source) {
+		if (!source) {
+			return destination;
+		}
+		forOwn(source, function(key, value) {
+			destination[key] = value;
+		});
+		return destination;
+	};
+
+	var forEach = function(array, callback) {
+		var length = array.length;
+		var index = -1;
+		while (++index < length) {
+			callback(array[index]);
+		}
+	};
+
+	var toString = object.toString;
+	var isArray = function(value) {
+		return toString.call(value) == '[object Array]';
+	};
+	var isObject = function(value) {
+		// This is a very simple check, but it’s good enough for what we need.
+		return toString.call(value) == '[object Object]';
+	};
+	var isString = function(value) {
+		return typeof value == 'string' ||
+			toString.call(value) == '[object String]';
+	};
+	var isFunction = function(value) {
+		// In a perfect world, the `typeof` check would be sufficient. However,
+		// in Chrome 1–12, `typeof /x/ == 'object'`, and in IE 6–8
+		// `typeof alert == 'object'` and similar for other host objects.
+		return typeof value == 'function' ||
+			toString.call(value) == '[object Function]';
+	};
+
+	/*--------------------------------------------------------------------------*/
+
+	// https://mathiasbynens.be/notes/javascript-escapes#single
+	var singleEscapes = {
+		'"': '\\"',
+		'\'': '\\\'',
+		'\\': '\\\\',
+		'\b': '\\b',
+		'\f': '\\f',
+		'\n': '\\n',
+		'\r': '\\r',
+		'\t': '\\t'
+		// `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'.
+		// '\v': '\\x0B'
+	};
+	var regexSingleEscape = /["'\\\b\f\n\r\t]/;
+
+	var regexDigit = /[0-9]/;
+	var regexWhitelist = /[ !#-&\(-\[\]-~]/;
+
+	var jsesc = function(argument, options) {
+		// Handle options
+		var defaults = {
+			'escapeEverything': false,
+			'quotes': 'single',
+			'wrap': false,
+			'es6': false,
+			'json': false,
+			'compact': true,
+			'lowercaseHex': false,
+			'indent': '\t',
+			'__indent__': ''
+		};
+		var json = options && options.json;
+		if (json) {
+			defaults.quotes = 'double';
+			defaults.wrap = true;
+		}
+		options = extend(defaults, options);
+		if (options.quotes != 'single' && options.quotes != 'double') {
+			options.quotes = 'single';
+		}
+		var quote = options.quotes == 'double' ? '"' : '\'';
+		var compact = options.compact;
+		var indent = options.indent;
+		var oldIndent;
+		var newLine = compact ? '' : '\n';
+		var result;
+		var isEmpty = true;
+
+		if (json && argument && isFunction(argument.toJSON)) {
+			argument = argument.toJSON();
+		}
+
+		if (!isString(argument)) {
+			if (isArray(argument)) {
+				result = [];
+				options.wrap = true;
+				oldIndent = options.__indent__;
+				indent += oldIndent;
+				options.__indent__ = indent;
+				forEach(argument, function(value) {
+					isEmpty = false;
+					result.push(
+						(compact ? '' : indent) +
+						jsesc(value, options)
+					);
+				});
+				if (isEmpty) {
+					return '[]';
+				}
+				return '[' + newLine + result.join(',' + newLine) + newLine +
+					(compact ? '' : oldIndent) + ']';
+			} else if (!isObject(argument)) {
+				if (json) {
+					// For some values (e.g. `undefined`, `function` objects),
+					// `JSON.stringify(value)` returns `undefined` (which isn’t valid
+					// JSON) instead of `'null'`.
+					return JSON.stringify(argument) || 'null';
+				}
+				return String(argument);
+			} else { // it’s an object
+				result = [];
+				options.wrap = true;
+				oldIndent = options.__indent__;
+				indent += oldIndent;
+				options.__indent__ = indent;
+				forOwn(argument, function(key, value) {
+					isEmpty = false;
+					result.push(
+						(compact ? '' : indent) +
+						jsesc(key, options) + ':' +
+						(compact ? '' : ' ') +
+						jsesc(value, options)
+					);
+				});
+				if (isEmpty) {
+					return '{}';
+				}
+				return '{' + newLine + result.join(',' + newLine) + newLine +
+					(compact ? '' : oldIndent) + '}';
+			}
+		}
+
+		var string = argument;
+		// Loop over each code unit in the string and escape it
+		var index = -1;
+		var length = string.length;
+		var first;
+		var second;
+		var codePoint;
+		result = '';
+		while (++index < length) {
+			var character = string.charAt(index);
+			if (options.es6) {
+				first = string.charCodeAt(index);
+				if ( // check if it’s the start of a surrogate pair
+					first >= 0xD800 && first <= 0xDBFF && // high surrogate
+					length > index + 1 // there is a next code unit
+				) {
+					second = string.charCodeAt(index + 1);
+					if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
+						// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
+						codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
+						var hexadecimal = codePoint.toString(16);
+						if (!options.lowercaseHex) {
+							hexadecimal = hexadecimal.toUpperCase();
+						}
+						result += '\\u{' + hexadecimal + '}';
+						index++;
+						continue;
+					}
+				}
+			}
+			if (!options.escapeEverything) {
+				if (regexWhitelist.test(character)) {
+					// It’s a printable ASCII character that is not `"`, `'` or `\`,
+					// so don’t escape it.
+					result += character;
+					continue;
+				}
+				if (character == '"') {
+					result += quote == character ? '\\"' : character;
+					continue;
+				}
+				if (character == '\'') {
+					result += quote == character ? '\\\'' : character;
+					continue;
+				}
+			}
+			if (
+				character == '\0' &&
+				!json &&
+				!regexDigit.test(string.charAt(index + 1))
+			) {
+				result += '\\0';
+				continue;
+			}
+			if (regexSingleEscape.test(character)) {
+				// no need for a `hasOwnProperty` check here
+				result += singleEscapes[character];
+				continue;
+			}
+			var charCode = character.charCodeAt(0);
+			var hexadecimal = charCode.toString(16);
+			if (!options.lowercaseHex) {
+				hexadecimal = hexadecimal.toUpperCase();
+			}
+			var longhand = hexadecimal.length > 2 || json;
+			var escaped = '\\' + (longhand ? 'u' : 'x') +
+				('0000' + hexadecimal).slice(longhand ? -4 : -2);
+			result += escaped;
+			continue;
+		}
+		if (options.wrap) {
+			result = quote + result + quote;
+		}
+		return result;
+	};
+
+	jsesc.version = '1.0.0';
+
+	/*--------------------------------------------------------------------------*/
+
+	// Some AMD build optimizers, like r.js, check for specific condition patterns
+	// like the following:
+	if (
+		typeof define == 'function' &&
+		typeof define.amd == 'object' &&
+		define.amd
+	) {
+		define(function() {
+			return jsesc;
+		});
+	}	else if (freeExports && !freeExports.nodeType) {
+		if (freeModule) { // in Node.js or RingoJS v0.8.0+
+			freeModule.exports = jsesc;
+		} else { // in Narwhal or RingoJS v0.7.0-
+			freeExports.jsesc = jsesc;
+		}
+	} else { // in Rhino or a web browser
+		root.jsesc = jsesc;
+	}
+
+}(this));