diff --git a/build/conformance.textproto b/build/conformance.textproto index bcbf158ecb..7c9407efc6 100644 --- a/build/conformance.textproto +++ b/build/conformance.textproto @@ -333,3 +333,25 @@ requirement: { "See also https://bit.ly/3wAsoj5" whitelist_regexp: "node_modules/" } + +# Disallow the general use of TextDecoder and TextEncoder, which is not +# available on all supported platforms. +requirement: { + type: BANNED_NAME_CALL + value: "TextDecoder" + value: "window.TextDecoder" + error_message: + "Using \"TextDecoder\" directly is not allowed; " + "because is not supported on Xbox and old browsers." + whitelist_regexp: "lib/util/string_utils.js" +} + +requirement: { + type: BANNED_NAME_CALL + value: "TextEncoder" + value: "window.TextEncoder" + error_message: + "Using \"TextEncoder\" directly is not allowed; " + "because is not supported on Xbox and old browsers." + whitelist_regexp: "lib/util/string_utils.js" +} diff --git a/demo/index.html b/demo/index.html index c376c27d50..409c16cb82 100644 --- a/demo/index.html +++ b/demo/index.html @@ -50,8 +50,6 @@ - - diff --git a/docs/tutorials/upgrade.md b/docs/tutorials/upgrade.md index 800bffbcaa..3aefa2bd9d 100644 --- a/docs/tutorials/upgrade.md +++ b/docs/tutorials/upgrade.md @@ -25,6 +25,7 @@ application: - TextDecoder/TextEncoder platform support or polyfill required (affects Xbox One, but not evergreen browsers); we suggest the polyfill [https://github.com/anonyco/FastestSmallestTextEncoderDecoder](fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js) + Fallback included by default in v4.2 - Support removed: - IE11 support removed diff --git a/lib/util/string_utils.js b/lib/util/string_utils.js index 895fd08358..61ad403c68 100644 --- a/lib/util/string_utils.js +++ b/lib/util/string_utils.js @@ -36,19 +36,38 @@ shaka.util.StringUtils = class { if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) { uint8 = uint8.subarray(3); } - - // Use the TextDecoder interface to decode the text. This has the advantage - // compared to the previously-standard decodeUriComponent that it will - // continue parsing even if it finds an invalid UTF8 character, rather than - // stop and throw an error. - const utf8decoder = new TextDecoder(); - const decoded = utf8decoder.decode(uint8); - if (decoded.includes('\uFFFD')) { - shaka.log.alwaysError('Decoded string contains an "unknown character" ' + - 'codepoint. That probably means the UTF8 ' + - 'encoding was incorrect!'); + if (window.TextDecoder) { + // Use the TextDecoder interface to decode the text. This has the + // advantage compared to the previously-standard decodeUriComponent that + // it will continue parsing even if it finds an invalid UTF8 character, + // rather than stop and throw an error. + const utf8decoder = new TextDecoder(); + const decoded = utf8decoder.decode(uint8); + if (decoded.includes('\uFFFD')) { + shaka.log.alwaysError('Decoded string contains an "unknown character' + + '" codepoint. That probably means the UTF8 ' + + 'encoding was incorrect!'); + } + return decoded; + } else { + // http://stackoverflow.com/a/13691499 + const utf8 = shaka.util.StringUtils.fromCharCode(uint8); + // This converts each character in the string to an escape sequence. If + // the character is in the ASCII range, it is not converted; otherwise it + // is converted to a URI escape sequence. + // Example: '\x67\x35\xe3\x82\xac' -> 'g#%E3%82%AC' + const escaped = escape(utf8); + // Decode the escaped sequence. This will interpret UTF-8 sequences into + // the correct character. + // Example: 'g#%E3%82%AC' -> 'g#€' + try { + return decodeURIComponent(escaped); + } catch (e) { + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.TEXT, + shaka.util.Error.Code.BAD_ENCODING); + } } - return decoded; } @@ -141,8 +160,30 @@ shaka.util.StringUtils = class { * @export */ static toUTF8(str) { - const utf8Encoder = new TextEncoder(); - return shaka.util.BufferUtils.toArrayBuffer(utf8Encoder.encode(str)); + if (window.TextEncoder) { + const utf8Encoder = new TextEncoder(); + return shaka.util.BufferUtils.toArrayBuffer(utf8Encoder.encode(str)); + } else { + // http://stackoverflow.com/a/13691499 + // Converts the given string to a URI encoded string. If a character + // falls in the ASCII range, it is not converted; otherwise it will be + // converted to a series of URI escape sequences according to UTF-8. + // Example: 'g#€' -> 'g#%E3%82%AC' + const encoded = encodeURIComponent(str); + // Convert each escape sequence individually into a character. Each + // escape sequence is interpreted as a code-point, so if an escape + // sequence happens to be part of a multi-byte sequence, each byte will + // be converted to a single character. + // Example: 'g#%E3%82%AC' -> '\x67\x35\xe3\x82\xac' + const utf8 = unescape(encoded); + + const result = new Uint8Array(utf8.length); + for (let i = 0; i < utf8.length; i++) { + const item = utf8[i]; + result[i] = item.charCodeAt(0); + } + return shaka.util.BufferUtils.toArrayBuffer(result); + } } diff --git a/package-lock.json b/package-lock.json index 0c7a9b7dc3..4e731425e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,6 @@ "eslint-config-google": "^0.14.0", "eslint-plugin-shaka-rules": "file:./build/eslint-plugin-shaka-rules", "esprima": "^4.0.1", - "fastestsmallesttextencoderdecoder": "^1.0.22", "fontfaceonload": "^1.0.2", "google-closure-compiler-java": "^20220301.0.0", "google-closure-deps": "https://gitpkg.now.sh/google/closure-library/closure-deps?d7736da6", @@ -4137,12 +4136,6 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, - "node_modules/fastestsmallesttextencoderdecoder": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", - "dev": true - }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -11643,12 +11636,6 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, - "fastestsmallesttextencoderdecoder": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", - "dev": true - }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", diff --git a/package.json b/package.json index 405401d709..53a8e0b732 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "eslint-config-google": "^0.14.0", "eslint-plugin-shaka-rules": "file:./build/eslint-plugin-shaka-rules", "esprima": "^4.0.1", - "fastestsmallesttextencoderdecoder": "^1.0.22", "fontfaceonload": "^1.0.2", "google-closure-compiler-java": "^20220301.0.0", "google-closure-deps": "https://gitpkg.now.sh/google/closure-library/closure-deps?d7736da6", @@ -71,7 +70,6 @@ "dialog-polyfill/dist/dialog-polyfill.js", "eme-encryption-scheme-polyfill/index.js", "es6-promise-polyfill/promise.min.js", - "fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js", "google-closure-library/closure/goog/base.js", "less/dist/less.js", "material-design-lite/dist/material.indigo-blue.min.css",