Browse Source

Add a polyfill for TextEncoder/TextDecoder for Edge

JC Brand 6 years ago
parent
commit
bf76b3b486
5 changed files with 222 additions and 22 deletions
  1. 203 10
      dist/converse.js
  2. 5 0
      package-lock.json
  3. 3 0
      package.json
  4. 6 8
      src/utils/core.js
  5. 5 4
      webpack.config.js

+ 203 - 10
dist/converse.js

@@ -7507,6 +7507,205 @@ return Promise$2;
 
 /***/ }),
 
+/***/ "./node_modules/fast-text-encoding/text.js":
+/*!*************************************************!*\
+  !*** ./node_modules/fast-text-encoding/text.js ***!
+  \*************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* WEBPACK VAR INJECTION */(function(global) {/*
+ * Copyright 2017 Sam Thorogood. All rights reserved.
+ *
+ * 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 Polyfill for TextEncoder and TextDecoder.
+ *
+ * You probably want `text.min.js`, and not this file directly.
+ */
+
+(function(scope) {
+'use strict';
+
+// fail early
+if (scope['TextEncoder'] && scope['TextDecoder']) {
+  return false;
+}
+
+/**
+ * @constructor
+ * @param {string=} utfLabel
+ */
+function FastTextEncoder(utfLabel='utf-8') {
+  if (utfLabel !== 'utf-8') {
+    throw new RangeError(
+      `Failed to construct 'TextEncoder': The encoding label provided ('${utfLabel}') is invalid.`);
+  }
+}
+
+Object.defineProperty(FastTextEncoder.prototype, 'encoding', {value: 'utf-8'});
+
+/**
+ * @param {string} string
+ * @param {{stream: boolean}=} options
+ * @return {!Uint8Array}
+ */
+FastTextEncoder.prototype.encode = function(string, options={stream: false}) {
+  if (options.stream) {
+    throw new Error(`Failed to encode: the 'stream' option is unsupported.`);
+  }
+
+  let pos = 0;
+  const len = string.length;
+  const out = [];
+
+  let at = 0;  // output position
+  let tlen = Math.max(32, len + (len >> 1) + 7);  // 1.5x size
+  let target = new Uint8Array((tlen >> 3) << 3);  // ... but at 8 byte offset
+
+  while (pos < len) {
+    let value = string.charCodeAt(pos++);
+    if (value >= 0xd800 && value <= 0xdbff) {
+      // high surrogate
+      if (pos < len) {
+        const extra = string.charCodeAt(pos);
+        if ((extra & 0xfc00) === 0xdc00) {
+          ++pos;
+          value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
+        }
+      }
+      if (value >= 0xd800 && value <= 0xdbff) {
+        continue;  // drop lone surrogate
+      }
+    }
+
+    // expand the buffer if we couldn't write 4 bytes
+    if (at + 4 > target.length) {
+      tlen += 8;  // minimum extra
+      tlen *= (1.0 + (pos / string.length) * 2);  // take 2x the remaining
+      tlen = (tlen >> 3) << 3;  // 8 byte offset
+
+      const update = new Uint8Array(tlen);
+      update.set(target);
+      target = update;
+    }
+
+    if ((value & 0xffffff80) === 0) {  // 1-byte
+      target[at++] = value;  // ASCII
+      continue;
+    } else if ((value & 0xfffff800) === 0) {  // 2-byte
+      target[at++] = ((value >>  6) & 0x1f) | 0xc0;
+    } else if ((value & 0xffff0000) === 0) {  // 3-byte
+      target[at++] = ((value >> 12) & 0x0f) | 0xe0;
+      target[at++] = ((value >>  6) & 0x3f) | 0x80;
+    } else if ((value & 0xffe00000) === 0) {  // 4-byte
+      target[at++] = ((value >> 18) & 0x07) | 0xf0;
+      target[at++] = ((value >> 12) & 0x3f) | 0x80;
+      target[at++] = ((value >>  6) & 0x3f) | 0x80;
+    } else {
+      // FIXME: do we care
+      continue;
+    }
+
+    target[at++] = (value & 0x3f) | 0x80;
+  }
+
+  return target.slice(0, at);
+}
+
+/**
+ * @constructor
+ * @param {string=} utfLabel
+ * @param {{fatal: boolean}=} options
+ */
+function FastTextDecoder(utfLabel='utf-8', options={fatal: false}) {
+  if (utfLabel !== 'utf-8') {
+    throw new RangeError(
+      `Failed to construct 'TextDecoder': The encoding label provided ('${utfLabel}') is invalid.`);
+  }
+  if (options.fatal) {
+    throw new Error(`Failed to construct 'TextDecoder': the 'fatal' option is unsupported.`);
+  }
+}
+
+Object.defineProperty(FastTextDecoder.prototype, 'encoding', {value: 'utf-8'});
+
+Object.defineProperty(FastTextDecoder.prototype, 'fatal', {value: false});
+
+Object.defineProperty(FastTextDecoder.prototype, 'ignoreBOM', {value: false});
+
+/**
+ * @param {(!ArrayBuffer|!ArrayBufferView)} buffer
+ * @param {{stream: boolean}=} options
+ */
+FastTextDecoder.prototype.decode = function(buffer, options={stream: false}) {
+  if (options['stream']) {
+    throw new Error(`Failed to decode: the 'stream' option is unsupported.`);
+  }
+
+  const bytes = new Uint8Array(buffer);
+  let pos = 0;
+  const len = bytes.length;
+  const out = [];
+
+  while (pos < len) {
+    const byte1 = bytes[pos++];
+    if (byte1 === 0) {
+      break;  // NULL
+    }
+  
+    if ((byte1 & 0x80) === 0) {  // 1-byte
+      out.push(byte1);
+    } else if ((byte1 & 0xe0) === 0xc0) {  // 2-byte
+      const byte2 = bytes[pos++] & 0x3f;
+      out.push(((byte1 & 0x1f) << 6) | byte2);
+    } else if ((byte1 & 0xf0) === 0xe0) {
+      const byte2 = bytes[pos++] & 0x3f;
+      const byte3 = bytes[pos++] & 0x3f;
+      out.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3);
+    } else if ((byte1 & 0xf8) === 0xf0) {
+      const byte2 = bytes[pos++] & 0x3f;
+      const byte3 = bytes[pos++] & 0x3f;
+      const byte4 = bytes[pos++] & 0x3f;
+
+      // this can be > 0xffff, so possibly generate surrogates
+      let codepoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
+      if (codepoint > 0xffff) {
+        // codepoint &= ~0x10000;
+        codepoint -= 0x10000;
+        out.push((codepoint >>> 10) & 0x3ff | 0xd800)
+        codepoint = 0xdc00 | codepoint & 0x3ff;
+      }
+      out.push(codepoint);
+    } else {
+      // FIXME: we're ignoring this
+    }
+  }
+
+  return String.fromCharCode.apply(null, out);
+}
+
+scope['TextEncoder'] = FastTextEncoder;
+scope['TextDecoder'] = FastTextDecoder;
+
+}(typeof window !== 'undefined' ? window : (typeof global !== 'undefined' ? global : this)));
+
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js")))
+
+/***/ }),
+
 /***/ "./node_modules/filesize/lib/filesize.js":
 /*!***********************************************!*\
   !*** ./node_modules/filesize/lib/filesize.js ***!
@@ -81471,12 +81670,12 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 /*global define, escape, window, Uint8Array */
 (function (root, factory) {
   if (true) {
-    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"), __webpack_require__(/*! es6-promise */ "./node_modules/es6-promise/dist/es6-promise.auto.js"), __webpack_require__(/*! lodash.noconflict */ "./src/lodash.noconflict.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"), __webpack_require__(/*! strophe */ "./node_modules/strophe.js/strophe.js"), __webpack_require__(/*! uri */ "./node_modules/urijs/src/URI.js"), __webpack_require__(/*! templates/audio.html */ "./src/templates/audio.html"), __webpack_require__(/*! templates/file.html */ "./src/templates/file.html"), __webpack_require__(/*! templates/image.html */ "./src/templates/image.html"), __webpack_require__(/*! templates/video.html */ "./src/templates/video.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
+    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"), __webpack_require__(/*! es6-promise */ "./node_modules/es6-promise/dist/es6-promise.auto.js"), __webpack_require__(/*! fast-text-encoding */ "./node_modules/fast-text-encoding/text.js"), __webpack_require__(/*! lodash.noconflict */ "./src/lodash.noconflict.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"), __webpack_require__(/*! strophe */ "./node_modules/strophe.js/strophe.js"), __webpack_require__(/*! uri */ "./node_modules/urijs/src/URI.js"), __webpack_require__(/*! templates/audio.html */ "./src/templates/audio.html"), __webpack_require__(/*! templates/file.html */ "./src/templates/file.html"), __webpack_require__(/*! templates/image.html */ "./src/templates/image.html"), __webpack_require__(/*! templates/video.html */ "./src/templates/video.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
 				__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
 				(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
 				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
   } else {}
-})(void 0, function (sizzle, Promise, _, Backbone, Strophe, URI, tpl_audio, tpl_file, tpl_image, tpl_video) {
+})(void 0, function (sizzle, Promise, FastTextEncoding, _, Backbone, Strophe, URI, tpl_audio, tpl_file, tpl_image, tpl_video) {
   "use strict";
 
   Strophe = Strophe.Strophe;
@@ -82300,17 +82499,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
   };
 
   u.arrayBufferToString = function (ab) {
-    return new Uint8Array(ab).reduce((data, byte) => data + String.fromCharCode(byte), '');
+    return new TextDecoder("utf-8").decode(ab);
   };
 
   u.stringToArrayBuffer = function (string) {
-    const len = string.length,
-          bytes = new Uint8Array(len);
-
-    for (let i = 0; i < len; i++) {
-      bytes[i] = string.charCodeAt(i);
-    }
-
+    const bytes = new TextEncoder("utf-8").encode(string);
     return bytes.buffer;
   };
 

+ 5 - 0
package-lock.json

@@ -4078,6 +4078,11 @@
       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
       "dev": true
     },
+    "fast-text-encoding": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz",
+      "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ=="
+    },
     "fastparse": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",

+ 3 - 0
package.json

@@ -90,5 +90,8 @@
     "webpack": "^4.0.1",
     "webpack-cli": "^2.1.4",
     "xss": "^0.3.3"
+  },
+  "dependencies": {
+    "fast-text-encoding": "^1.0.0"
   }
 }

+ 6 - 8
src/utils/core.js

@@ -12,6 +12,7 @@
         define([
             "sizzle",
             "es6-promise",
+            "fast-text-encoding",
             "lodash.noconflict",
             "backbone",
             "strophe",
@@ -39,6 +40,7 @@
         root.converse_utils = factory(
             root.sizzle,
             root.Promise,
+            null,
             root._,
             root.Backbone,
             Strophe
@@ -47,6 +49,7 @@
 }(this, function (
         sizzle,
         Promise,
+        FastTextEncoding,
         _,
         Backbone,
         Strophe,
@@ -869,17 +872,12 @@
     };
 
     u.arrayBufferToString = function (ab) {
-        return (new Uint8Array(ab)).reduce((data, byte) => data + String.fromCharCode(byte), '');
+        return new TextDecoder("utf-8").decode(ab);
     };
 
     u.stringToArrayBuffer = function (string) {
-        const len = string.length,
-              bytes = new Uint8Array(len);
-
-        for (let i = 0; i < len; i++) {
-            bytes[i] = string.charCodeAt(i)
-        }
-        return bytes.buffer
+        const bytes = new TextEncoder("utf-8").encode(string);
+        return bytes.buffer;
     };
 
     u.arrayBufferToBase64 = function (ab) {

+ 5 - 4
webpack.config.js

@@ -80,7 +80,6 @@ const config = {
             "IPv6":                     path.resolve(__dirname, "node_modules/urijs/src/IPv6"),
             "SecondLevelDomains":       path.resolve(__dirname, "node_modules/urijs/src/SecondLevelDomains"),
             "awesomplete":              path.resolve(__dirname, "node_modules/awesomplete-avoid-xss/awesomplete"),
-            "formdata-polyfill":        path.resolve(__dirname, "node_modules/formdata-polyfill/FormData"),
             "backbone":                 path.resolve(__dirname, "node_modules/backbone/backbone"),
             "backbone.browserStorage":  path.resolve(__dirname, "node_modules/backbone.browserStorage/backbone.browserStorage"),
             "backbone.nativeview":      path.resolve(__dirname, "node_modules/backbone.nativeview/backbone.nativeview"),
@@ -91,6 +90,8 @@ const config = {
             "crypto":                   path.resolve(__dirname, "node_modules/otr/build/dep/crypto"),
             "es6-promise":              path.resolve(__dirname, "node_modules/es6-promise/dist/es6-promise.auto"),
             "filesize":                 path.resolve(__dirname, "node_modules/filesize/lib/filesize"),
+            "fast-text-encoding":       path.resolve(__dirname, "node_modules/fast-text-encoding/text"),
+            "formdata-polyfill":        path.resolve(__dirname, "node_modules/formdata-polyfill/FormData"),
             "jed":                      path.resolve(__dirname, "node_modules/jed/jed"),
             "jquery":                   path.resolve(__dirname, "src/jquery-stub"),
             "lodash":                   path.resolve(__dirname, "node_modules/lodash/lodash"),
@@ -107,14 +108,14 @@ const config = {
             "snabbdom-style":           path.resolve(__dirname, "node_modules/snabbdom/dist/snabbdom-style"),
             "strophe":                  path.resolve(__dirname, "node_modules/strophe.js/strophe"),
             "strophe.ping":             path.resolve(__dirname, "node_modules/strophejs-plugin-ping/strophe.ping"),
-            "utils/emoji":              path.resolve(__dirname, "src/utils/emoji"),
-            "utils/form":               path.resolve(__dirname, "src/utils/form"),
-            "utils/muc":                path.resolve(__dirname, "src/utils/muc"),
             "strophe.rsm":              path.resolve(__dirname, "node_modules/strophejs-plugin-rsm/strophe.rsm"),
             "tovnode":                  path.resolve(__dirname, "node_modules/snabbdom/dist/tovnode"),
             "underscore":               path.resolve(__dirname, "src/underscore-shim"),
             "uri":                      path.resolve(__dirname, "node_modules/urijs/src/URI"),
             "utils/core":               path.resolve(__dirname, "src/utils/core"),
+            "utils/emoji":              path.resolve(__dirname, "src/utils/emoji"),
+            "utils/form":               path.resolve(__dirname, "src/utils/form"),
+            "utils/muc":                path.resolve(__dirname, "src/utils/muc"),
             "vdom-parser":              path.resolve(__dirname, "node_modules/vdom-parser/dist"),
             "xss":                      path.resolve(__dirname, "node_modules/xss/dist/xss")
         }