[Pkg-javascript-commits] [node-text-encoding] 01/03: Imported Upstream version 0.5.3
Julien Puydt
julien.puydt at laposte.net
Fri Sep 11 14:09:25 UTC 2015
This is an automated email from the git hooks/post-receive script.
jpuydt-guest pushed a commit to branch master
in repository node-text-encoding.
commit b41465978987b41b5b7b828f70f87d36c104afce
Author: Julien Puydt <julien.puydt at laposte.net>
Date: Fri Sep 11 15:50:57 2015 +0200
Imported Upstream version 0.5.3
---
LICENSE.md | 202 +++
README.md | 103 ++
bower.json | 22 +
examples-no-indexes.html | 58 +
examples.html | 59 +
index.js | 20 +
lib/encoding-indexes.js | 54 +
lib/encoding.js | 3106 +++++++++++++++++++++++++++++++++++++++++++
package.json | 28 +
test/test-big5.js | 22 +
test/test-euc-jp.js | 22 +
test/test-euc-kr.js | 22 +
test/test-gbk.js | 22 +
test/test-iso-2022-jp.js | 22 +
test/test-misc.js | 321 +++++
test/test-shift_jis.js | 22 +
test/test-utf.js | 163 +++
test/test-x-user-defined.js | 29 +
test/testharness.css | 92 ++
test/testharness.js | 2121 +++++++++++++++++++++++++++++
test/testharnessreport.js | 380 ++++++
test/tests.html | 31 +
util/externs.js | 28 +
23 files changed, 6949 insertions(+)
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..6d00d2e
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2011
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f3e1cc2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,103 @@
+text-encoding
+==============
+
+This is a polyfill for the [Encoding Living Standard](https://encoding.spec.whatwg.org/)
+API for the Web, allowing encoding and decoding of textual data to and from Typed Array
+buffers for binary data in JavaScript.
+
+By default it adheres to the spec and does not support *encoding* to non-UTF encodings,
+only *decoding*. It is also implemented to match the specification's algorithms, rather
+than for performance. The intended use is within Web pages, so it has no dependency
+on server frameworks or particular module schemes.
+
+Basic examples and tests are included.
+
+### Install ###
+
+There are a few ways you can get the `text-encoding` library.
+
+#### Node ####
+
+`text-encoding` is on `npm`. Simply run:
+
+```js
+npm install text-encoding
+```
+
+Or add it to your `package.json` dependencies.
+
+#### Bower ####
+
+`text-encoding` is on `bower` as well. Install with bower like so:
+
+```js
+bower install text-encoding
+```
+
+Or add it to your `bower.json` dependencies.
+
+### HTML Page Usage ###
+
+```html
+ <!-- Required for non-UTF encodings -->
+ <script src="encoding-indexes.js"></script>
+ <script src="encoding.js"></script>
+```
+
+### API Overview ###
+
+Basic Usage
+
+```js
+ var uint8array = TextEncoder(encoding).encode(string);
+ var string = TextDecoder(encoding).decode(uint8array);
+```
+
+Streaming Decode
+
+```js
+ var string = "", decoder = TextDecoder(encoding), buffer;
+ while (buffer = next_chunk()) {
+ string += decoder.decode(buffer, {stream:true});
+ }
+ string += decoder.decode(); // finish the stream
+```
+
+### Encodings ###
+
+All encodings from the Encoding specification are supported:
+
+utf-8 ibm866 iso-8859-2 iso-8859-3 iso-8859-4 iso-8859-5 iso-8859-6 iso-8859-7 iso-8859-8 iso-8859-8-i iso-8859-10 iso-8859-13 iso-8859-14 iso-8859-15 iso-8859-16 koi8-r koi8-u macintosh windows-874 windows-1250 windows-1251 windows-1252 windows-1253 windows-1254 windows-1255 windows-1256 windows-1257 windows-1258 x-mac-cyrillic gb18030 hz-gb-2312 big5 euc-jp iso-2022-jp shift_jis euc-kr replacement utf-16be utf-16le x-user-defined
+
+(Some encodings may be supported under other names, e.g. ascii, iso-8859-1, etc.
+See [Encoding](https://encoding.spec.whatwg.org/) for additional labels for each encoding.)
+
+Encodings other than **utf-8**, **utf-16le** and **utf-16be** require an additional
+`encoding-indexes.js` file to be included. It is rather large
+(596kB uncompressed, 188kB gzipped); portions may be deleted if
+support for some encodings is not required.
+
+### Non-Standard Behavior ###
+
+As required by the specification, only encoding to **utf-8**,
+**utf-16le** and **utf-16be** is supported. If you want to try it out, you can
+force a non-standard behavior by passing the `NONSTANDARD_allowLegacyEncoding`
+option to TextEncoder. For example:
+
+```js
+var uint8array = new TextEncoder(
+ 'windows-1252', { NONSTANDARD_allowLegacyEncoding: true }).encode(text);
+```
+
+But note that the above won't work if you're using the polyfill in a browser that
+natively supports the TextEncoder API natively, since the polyfill won't be used!
+You'd need to fork and modify the polyfill to... not be a polyfill.
+
+To support the legacy encodings (which may be stateful), the TextEncoder `encode()`
+method accepts an optional dictionary and `stream` option,
+e.g. `encoder.encode(string, {stream: true});` This is not needed for the
+stateless UTF encodings since the input is always in complete code points.
+
+The polyfill also allows construction of encoder/decoder objects without the
+`new` keyword. (This non-standard behavior is supported for constructing DOM
+objects in Firefox but not other browsers.)
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..63dd654
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,22 @@
+{
+ "name": "text-encoding",
+ "version": "0.5.3",
+ "homepage": "https://github.com/inexorabletash/text-encoding",
+ "authors": [
+ "Joshua Bell <inexorabletash at gmail.com>"
+ ],
+ "description": "Polyfill for the Encoding Living Standard's API",
+ "main": [ "lib/encoding.js", "lib/encoding-indexes.js" ],
+ "keywords": [
+ "decoding",
+ "encoding",
+ "living",
+ "standards"
+ ],
+ "license": "Apache-2.0",
+ "ignore": [
+ "**/.*",
+ "test",
+ "*examples*.html"
+ ]
+}
diff --git a/examples-no-indexes.html b/examples-no-indexes.html
new file mode 100644
index 0000000..58d70d5
--- /dev/null
+++ b/examples-no-indexes.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<script src="lib/encoding.js"></script>
+<script>
+function encodeArrayOfStrings(strings, encoding) {
+ var encoder, encoded, len, i, bytes, view, offset;
+
+ encoder = new TextEncoder(encoding);
+ encoded = [];
+
+ len = Uint32Array.BYTES_PER_ELEMENT;
+ for (i = 0; i < strings.length; i += 1) {
+ len += Uint32Array.BYTES_PER_ELEMENT;
+ encoded[i] = new TextEncoder(encoding).encode(strings[i]);
+ len += encoded[i].byteLength;
+ }
+
+ bytes = new Uint8Array(len);
+ view = new DataView(bytes.buffer);
+ offset = 0;
+
+ view.setUint32(offset, strings.length);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ for (i = 0; i < encoded.length; i += 1) {
+ len = encoded[i].byteLength;
+ view.setUint32(offset, len);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ bytes.set(encoded[i], offset);
+ offset += len;
+ }
+ return bytes.buffer;
+}
+
+function decodeArrayOfStrings(buffer, encoding) {
+ var decoder, view, offset, num_strings, strings, i, len;
+
+ decoder = new TextDecoder(encoding);
+ view = new DataView(buffer);
+ offset = 0;
+ strings = [];
+
+ num_strings = view.getUint32(offset);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ for (i = 0; i < num_strings; i += 1) {
+ len = view.getUint32(offset);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ strings[i] = decoder.decode(
+ new DataView(view.buffer, offset, len));
+ offset += len;
+ }
+ return strings;
+}
+
+var strings = ["Hello", "string", "encoding!"];
+var buffer = encodeArrayOfStrings(strings, "utf-8");
+var results = decodeArrayOfStrings(buffer, "utf-8");
+document.write("Encoded " + JSON.stringify(strings) + "<br>");
+document.write("Decoded " + JSON.stringify(results) + "<br>");
+</script>
diff --git a/examples.html b/examples.html
new file mode 100644
index 0000000..b60dd2b
--- /dev/null
+++ b/examples.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<script src="lib/encoding-indexes.js"></script>
+<script src="lib/encoding.js"></script>
+<script>
+function encodeArrayOfStrings(strings, encoding) {
+ var encoder, encoded, len, i, bytes, view, offset;
+
+ encoder = new TextEncoder(encoding);
+ encoded = [];
+
+ len = Uint32Array.BYTES_PER_ELEMENT;
+ for (i = 0; i < strings.length; i += 1) {
+ len += Uint32Array.BYTES_PER_ELEMENT;
+ encoded[i] = new TextEncoder(encoding).encode(strings[i]);
+ len += encoded[i].byteLength;
+ }
+
+ bytes = new Uint8Array(len);
+ view = new DataView(bytes.buffer);
+ offset = 0;
+
+ view.setUint32(offset, strings.length);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ for (i = 0; i < encoded.length; i += 1) {
+ len = encoded[i].byteLength;
+ view.setUint32(offset, len);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ bytes.set(encoded[i], offset);
+ offset += len;
+ }
+ return bytes.buffer;
+}
+
+function decodeArrayOfStrings(buffer, encoding) {
+ var decoder, view, offset, num_strings, strings, i, len;
+
+ decoder = new TextDecoder(encoding);
+ view = new DataView(buffer);
+ offset = 0;
+ strings = [];
+
+ num_strings = view.getUint32(offset);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ for (i = 0; i < num_strings; i += 1) {
+ len = view.getUint32(offset);
+ offset += Uint32Array.BYTES_PER_ELEMENT;
+ strings[i] = decoder.decode(
+ new DataView(view.buffer, offset, len));
+ offset += len;
+ }
+ return strings;
+}
+
+var strings = ["Hello", "string", "encoding!"];
+var buffer = encodeArrayOfStrings(strings, "utf-8");
+var results = decodeArrayOfStrings(buffer, "utf-8");
+document.write("Encoded " + JSON.stringify(strings) + "<br>");
+document.write("Decoded " + JSON.stringify(results) + "<br>");
+</script>
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..819b19d
--- /dev/null
+++ b/index.js
@@ -0,0 +1,20 @@
+// Copyright 2014 Joshua Bell. 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.
+
+var encoding = require("./lib/encoding.js");
+
+module.exports = {
+ TextEncoder: encoding.TextEncoder,
+ TextDecoder: encoding.TextDecoder,
+};
diff --git a/lib/encoding-indexes.js b/lib/encoding-indexes.js
new file mode 100644
index 0000000..122de54
--- /dev/null
+++ b/lib/encoding-indexes.js
@@ -0,0 +1,54 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+// Indexes from: https://encoding.spec.whatwg.org/indexes.json
+
+(function(global) {
+ 'use strict';
+ global["encoding-indexes"] = {
+ "big5":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,nu [...]
+ "euc-kr":[44034,44035,44037,44038,44043,44044,44045,44046,44047,44056,44062,44063,44065,44066,44067,44069,44070,44071,44072,44073,44074,44075,44078,44082,44083,44084,null,null,null,null,null,null,44085,44086,44087,44090,44091,44093,44094,44095,44097,44098,44099,44100,44101,44102,44103,44104,44105,44106,44108,44110,44111,44112,44113,44114,44115,44117,null,null,null,null,null,null,44118,44119,44121,44122,44123,44125,44126,44127,44128,44129,44130,44131,44132,44133,44134,44135,44136,44137, [...]
+ "gb18030":[19970,19972,19973,19974,19983,19986,19991,19999,20000,20001,20003,20006,20009,20014,20015,20017,20019,20021,20023,20028,20032,20033,20034,20036,20038,20042,20049,20053,20055,20058,20059,20066,20067,20068,20069,20071,20072,20074,20075,20076,20077,20078,20079,20082,20084,20085,20086,20087,20088,20089,20090,20091,20092,20093,20095,20096,20097,20098,20099,20100,20101,20103,20106,20112,20118,20119,20121,20124,20125,20126,20131,20138,20143,20144,20145,20148,20150,20151,20152,20153 [...]
+ "gb18030-ranges":[[0,128],[36,165],[38,169],[45,178],[50,184],[81,216],[89,226],[95,235],[96,238],[100,244],[103,248],[104,251],[105,253],[109,258],[126,276],[133,284],[148,300],[172,325],[175,329],[179,334],[208,364],[306,463],[307,465],[308,467],[309,469],[310,471],[311,473],[312,475],[313,477],[341,506],[428,594],[443,610],[544,712],[545,716],[558,730],[741,930],[742,938],[749,962],[750,970],[805,1026],[819,1104],[820,1106],[7922,8209],[7924,8215],[7925,8218],[7927,8222],[7934,8231] [...]
+ "jis0208":[12288,12289,12290,65292,65294,12539,65306,65307,65311,65281,12443,12444,180,65344,168,65342,65507,65343,12541,12542,12445,12446,12291,20189,12293,12294,12295,12540,8213,8208,65295,65340,65374,8741,65372,8230,8229,8216,8217,8220,8221,65288,65289,12308,12309,65339,65341,65371,65373,12296,12297,12298,12299,12300,12301,12302,12303,12304,12305,65291,65293,177,215,247,65309,8800,65308,65310,8806,8807,8734,8756,9794,9792,176,8242,8243,8451,65509,65284,65504,65505,65285,65283,65286, [...]
+ "jis0212":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null [...]
+ "ibm866":[1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,9617,9618,9619,9474,9508,9569,9570,9558,9557,9571,9553,9559,9565,9564,9563,9488,9492,9524,9516,9500,9472,9532,9566,9567,9562,9556,9577,9574,9568,9552,9580,9575,9576,9572,9573,9561,9560,9554,9555,9579,9578,9496,9484,9608,9604,9612,9616,9600, [...]
+ "iso-8859-2":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,260,728,321,164,317,346,167,168,352,350,356,377,173,381,379,176,261,731,322,180,318,347,711,184,353,351,357,378,733,382,380,340,193,194,258,196,313,262,199,268,201,280,203,282,205,206,270,272,323,327,211,212,336,214,215,344,366,218,368,220,221,354,223,341,225,226,259,228,314,263,231,269,233,281,235,283,237,238,271,273,324,328,243,244,337,246, [...]
+ "iso-8859-3":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,294,728,163,164,null,292,167,168,304,350,286,308,173,null,379,176,295,178,179,180,181,293,183,184,305,351,287,309,189,null,380,192,193,194,null,196,266,264,199,200,201,202,203,204,205,206,207,null,209,210,211,212,288,214,215,284,217,218,219,220,364,348,223,224,225,226,null,228,267,265,231,232,233,234,235,236,237,238,239,null,241,242,243,244,2 [...]
+ "iso-8859-4":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,260,312,342,164,296,315,167,168,352,274,290,358,173,381,175,176,261,731,343,180,297,316,711,184,353,275,291,359,330,382,331,256,193,194,195,196,197,198,302,268,201,280,203,278,205,206,298,272,325,332,310,212,213,214,215,216,370,218,219,220,360,362,223,257,225,226,227,228,229,230,303,269,233,281,235,279,237,238,299,273,326,333,311,244,245,246, [...]
+ "iso-8859-5":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,173,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093, [...]
+ "iso-8859-6":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,null,null,null,164,null,null,null,null,null,null,null,1548,173,null,null,null,null,null,null,null,null,null,null,null,null,null,1563,null,null,null,1567,null,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,null,null,null,null,null,1600,1601,1602,1603,1604,1605,1 [...]
+ "iso-8859-7":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,8216,8217,163,8364,8367,166,167,168,169,890,171,172,173,null,8213,176,177,178,179,900,901,902,183,904,905,906,187,908,189,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,null,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,9 [...]
+ "iso-8859-8":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,null,162,163,164,165,166,167,168,169,215,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,247,187,188,189,190,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8215,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,149 [...]
+ "iso-8859-10":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,260,274,290,298,296,310,167,315,272,352,358,381,173,362,330,176,261,275,291,299,297,311,183,316,273,353,359,382,8213,363,331,256,193,194,195,196,197,198,302,268,201,280,203,278,205,206,207,208,325,332,211,212,213,214,360,216,370,218,219,220,221,222,223,257,225,226,227,228,229,230,303,269,233,281,235,279,237,238,239,240,326,333,243,244,245,24 [...]
+ "iso-8859-13":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,8221,162,163,164,8222,166,167,216,169,342,171,172,173,174,198,176,177,178,179,8220,181,182,183,248,185,343,187,188,189,190,230,260,302,256,262,196,197,280,274,268,201,377,278,290,310,298,315,352,323,325,211,332,213,214,215,370,321,346,362,220,379,381,223,261,303,257,263,228,229,281,275,269,233,378,279,291,311,299,316,353,324,326,243,333,245, [...]
+ "iso-8859-14":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,7682,7683,163,266,267,7690,167,7808,169,7810,7691,7922,173,174,376,7710,7711,288,289,7744,7745,182,7766,7809,7767,7811,7776,7923,7812,7813,7777,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,372,209,210,211,212,213,214,7786,216,217,218,219,220,221,374,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,373,24 [...]
+ "iso-8859-15":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,8364,165,352,167,353,169,170,171,172,173,174,175,176,177,178,179,381,181,182,183,382,185,186,187,338,339,376,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,24 [...]
+ "iso-8859-16":[128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,260,261,321,8364,8222,352,167,353,169,536,171,377,173,378,379,176,177,268,322,381,8221,182,183,382,269,537,187,338,339,376,380,192,193,194,258,196,262,198,199,200,201,202,203,204,205,206,207,272,323,210,211,212,336,214,346,368,217,218,219,220,280,538,223,224,225,226,259,228,263,230,231,232,233,234,235,236,237,238,239,273,324,242,243,244,337, [...]
+ "koi8-r":[9472,9474,9484,9488,9492,9496,9500,9508,9516,9524,9532,9600,9604,9608,9612,9616,9617,9618,9619,8992,9632,8729,8730,8776,8804,8805,160,8993,176,178,183,247,9552,9553,9554,1105,9555,9556,9557,9558,9559,9560,9561,9562,9563,9564,9565,9566,9567,9568,9569,1025,9570,9571,9572,9573,9574,9575,9576,9577,9578,9579,9580,169,1102,1072,1073,1094,1076,1077,1092,1075,1093,1080,1081,1082,1083,1084,1085,1086,1087,1103,1088,1089,1090,1091,1078,1074,1100,1099,1079,1096,1101,1097,1095,1098,1070,1 [...]
+ "koi8-u":[9472,9474,9484,9488,9492,9496,9500,9508,9516,9524,9532,9600,9604,9608,9612,9616,9617,9618,9619,8992,9632,8729,8730,8776,8804,8805,160,8993,176,178,183,247,9552,9553,9554,1105,1108,9556,1110,1111,9559,9560,9561,9562,9563,1169,1118,9566,9567,9568,9569,1025,1028,9571,1030,1031,9574,9575,9576,9577,9578,1168,1038,169,1102,1072,1073,1094,1076,1077,1092,1075,1093,1080,1081,1082,1083,1084,1085,1086,1087,1103,1088,1089,1090,1091,1078,1074,1100,1099,1079,1096,1101,1097,1095,1098,1070,1 [...]
+ "macintosh":[196,197,199,201,209,214,220,225,224,226,228,227,229,231,233,232,234,235,237,236,238,239,241,243,242,244,246,245,250,249,251,252,8224,176,162,163,167,8226,182,223,174,169,8482,180,168,8800,198,216,8734,177,8804,8805,165,181,8706,8721,8719,960,8747,170,186,937,230,248,191,161,172,8730,402,8776,8710,171,187,8230,160,192,195,213,338,339,8211,8212,8220,8221,8216,8217,247,9674,255,376,8260,8364,8249,8250,64257,64258,8225,183,8218,8222,8240,194,202,193,203,200,205,206,207,204,211 [...]
+ "windows-874":[8364,129,130,131,132,8230,134,135,136,137,138,139,140,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,152,153,154,155,156,157,158,159,160,3585,3586,3587,3588,3589,3590,3591,3592,3593,3594,3595,3596,3597,3598,3599,3600,3601,3602,3603,3604,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618,3619,3620,3621,3622,3623,3624,3625,3626,3627,3628,3629,3630,3631,3632,3633,3634,3635,3636,3637,3638,3639,3640,3641,3642,null,null,null,null,3647,3648,3649,3650,3651 [...]
+ "windows-1250":[8364,129,8218,131,8222,8230,8224,8225,136,8240,352,8249,346,356,381,377,144,8216,8217,8220,8221,8226,8211,8212,152,8482,353,8250,347,357,382,378,160,711,728,321,164,260,166,167,168,169,350,171,172,173,174,379,176,177,731,322,180,181,182,183,184,261,351,187,317,733,318,380,340,193,194,258,196,313,262,199,268,201,280,203,282,205,206,270,272,323,327,211,212,336,214,215,344,366,218,368,220,221,354,223,341,225,226,259,228,314,263,231,269,233,281,235,283,237,238,271,273,324,3 [...]
+ "windows-1251":[1026,1027,8218,1107,8222,8230,8224,8225,8364,8240,1033,8249,1034,1036,1035,1039,1106,8216,8217,8220,8221,8226,8211,8212,152,8482,1113,8250,1114,1116,1115,1119,160,1038,1118,1032,164,1168,166,167,1025,169,1028,171,172,173,174,1031,176,177,1030,1110,1169,181,182,183,1105,8470,1108,187,1112,1029,1109,1111,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073, [...]
+ "windows-1252":[8364,129,8218,402,8222,8230,8224,8225,710,8240,352,8249,338,141,381,143,144,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,157,382,376,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,2 [...]
+ "windows-1253":[8364,129,8218,402,8222,8230,8224,8225,136,8240,138,8249,140,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,152,8482,154,8250,156,157,158,159,160,901,902,163,164,165,166,167,168,169,null,171,172,173,174,8213,176,177,178,179,900,181,182,183,904,905,906,187,908,189,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,null,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,96 [...]
+ "windows-1254":[8364,129,8218,402,8222,8230,8224,8225,710,8240,352,8249,338,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,157,158,376,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,286,209,210,211,212,213,214,215,216,217,218,219,220,304,350,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,287,241,2 [...]
+ "windows-1255":[8364,129,8218,402,8222,8230,8224,8225,710,8240,138,8249,140,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,732,8482,154,8250,156,157,158,159,160,161,162,163,8362,165,166,167,168,169,215,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,247,187,188,189,190,191,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,null,1467,1468,1469,1470,1471,1472,1473,1474,1475,1520,1521,1522,1523,1524,null,null,null,null,null,null,null,1488,1489,1490,1491,1492,1493,1494,1495, [...]
+ "windows-1256":[8364,1662,8218,402,8222,8230,8224,8225,710,8240,1657,8249,338,1670,1688,1672,1711,8216,8217,8220,8221,8226,8211,8212,1705,8482,1681,8250,339,8204,8205,1722,160,1548,162,163,164,165,166,167,168,169,1726,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,1563,187,188,189,190,1567,1729,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,215,1591,1592,1593,1594,1600,1601,1602,1603,224,1604,226,1605,1606,1607 [...]
+ "windows-1257":[8364,129,8218,131,8222,8230,8224,8225,136,8240,138,8249,140,168,711,184,144,8216,8217,8220,8221,8226,8211,8212,152,8482,154,8250,156,175,731,159,160,null,162,163,164,null,166,167,216,169,342,171,172,173,174,198,176,177,178,179,180,181,182,183,248,185,343,187,188,189,190,230,260,302,256,262,196,197,280,274,268,201,377,278,290,310,298,315,352,323,325,211,332,213,214,215,370,321,346,362,220,379,381,223,261,303,257,263,228,229,281,275,269,233,378,279,291,311,299,316,353,324 [...]
+ "windows-1258":[8364,129,8218,402,8222,8230,8224,8225,710,8240,138,8249,338,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,732,8482,154,8250,339,157,158,376,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,258,196,197,198,199,200,201,202,203,768,205,206,207,272,209,777,211,212,416,214,215,216,217,218,219,220,431,771,223,224,225,226,259,228,229,230,231,232,233,234,235,769,237,238,239,273,241,8 [...]
+ "x-mac-cyrillic":[1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,8224,176,1168,163,167,8226,182,1030,174,169,8482,1026,1106,8800,1027,1107,8734,177,8804,8805,1110,181,1169,1032,1028,1108,1031,1111,1033,1113,1034,1114,1112,1029,172,8730,402,8776,8710,171,187,8230,160,1035,1115,1036,1116,1109,8211,8212,8220,8221,8216,8217,247,8222,1038,1118,1039,1119,8470,1025,1105,1103,1072,1 [...]
+};
+}(this));
diff --git a/lib/encoding.js b/lib/encoding.js
new file mode 100644
index 0000000..bbdd071
--- /dev/null
+++ b/lib/encoding.js
@@ -0,0 +1,3106 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+// If we're in node require encoding-indexes and attach it to the global.
+/**
+ * @fileoverview Global |this| required for resolving indexes in node.
+ * @suppress {globalThis}
+ */
+if (typeof module !== "undefined" && module.exports) {
+ this["encoding-indexes"] =
+ require("./encoding-indexes.js")["encoding-indexes"];
+}
+
+(function(global) {
+ 'use strict';
+
+ //
+ // Utilities
+ //
+
+ /**
+ * @param {number} a The number to test.
+ * @param {number} min The minimum value in the range, inclusive.
+ * @param {number} max The maximum value in the range, inclusive.
+ * @return {boolean} True if a >= min and a <= max.
+ */
+ function inRange(a, min, max) {
+ return min <= a && a <= max;
+ }
+
+ /**
+ * @param {number} n The numerator.
+ * @param {number} d The denominator.
+ * @return {number} The result of the integer division of n by d.
+ */
+ function div(n, d) {
+ return Math.floor(n / d);
+ }
+
+ /**
+ * @param {*} o
+ * @return {Object}
+ */
+ function ToDictionary(o) {
+ if (o === undefined) return {};
+ if (o === Object(o)) return o;
+ throw TypeError('Could not convert argument to dictionary');
+ }
+
+ /**
+ * @param {string} string Input string of UTF-16 code units.
+ * @return {!Array.<number>} Code points.
+ */
+ function stringToCodePoints(string) {
+ // https://heycam.github.io/webidl/#dfn-obtain-unicode
+
+ // 1. Let S be the DOMString value.
+ var s = String(string);
+
+ // 2. Let n be the length of S.
+ var n = s.length;
+
+ // 3. Initialize i to 0.
+ var i = 0;
+
+ // 4. Initialize U to be an empty sequence of Unicode characters.
+ var u = [];
+
+ // 5. While i < n:
+ while (i < n) {
+
+ // 1. Let c be the code unit in S at index i.
+ var c = s.charCodeAt(i);
+
+ // 2. Depending on the value of c:
+
+ // c < 0xD800 or c > 0xDFFF
+ if (c < 0xD800 || c > 0xDFFF) {
+ // Append to U the Unicode character with code point c.
+ u.push(c);
+ }
+
+ // 0xDC00 ≤ c ≤ 0xDFFF
+ else if (0xDC00 <= c && c <= 0xDFFF) {
+ // Append to U a U+FFFD REPLACEMENT CHARACTER.
+ u.push(0xFFFD);
+ }
+
+ // 0xD800 ≤ c ≤ 0xDBFF
+ else if (0xD800 <= c && c <= 0xDBFF) {
+ // 1. If i = n−1, then append to U a U+FFFD REPLACEMENT
+ // CHARACTER.
+ if (i === n - 1) {
+ u.push(0xFFFD);
+ }
+ // 2. Otherwise, i < n−1:
+ else {
+ // 1. Let d be the code unit in S at index i+1.
+ var d = string.charCodeAt(i + 1);
+
+ // 2. If 0xDC00 ≤ d ≤ 0xDFFF, then:
+ if (0xDC00 <= d && d <= 0xDFFF) {
+ // 1. Let a be c & 0x3FF.
+ var a = c & 0x3FF;
+
+ // 2. Let b be d & 0x3FF.
+ var b = d & 0x3FF;
+
+ // 3. Append to U the Unicode character with code point
+ // 2^16+2^10*a+b.
+ u.push(0x10000 + (a << 10) + b);
+
+ // 4. Set i to i+1.
+ i += 1;
+ }
+
+ // 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a
+ // U+FFFD REPLACEMENT CHARACTER.
+ else {
+ u.push(0xFFFD);
+ }
+ }
+ }
+
+ // 3. Set i to i+1.
+ i += 1;
+ }
+
+ // 6. Return U.
+ return u;
+ }
+
+ /**
+ * @param {!Array.<number>} code_points Array of code points.
+ * @return {string} string String of UTF-16 code units.
+ */
+ function codePointsToString(code_points) {
+ var s = '';
+ for (var i = 0; i < code_points.length; ++i) {
+ var cp = code_points[i];
+ if (cp <= 0xFFFF) {
+ s += String.fromCharCode(cp);
+ } else {
+ cp -= 0x10000;
+ s += String.fromCharCode((cp >> 10) + 0xD800,
+ (cp & 0x3FF) + 0xDC00);
+ }
+ }
+ return s;
+ }
+
+
+ //
+ // Implementation of Encoding specification
+ // https://encoding.spec.whatwg.org/
+ //
+
+ //
+ // 3. Terminology
+ //
+
+ /**
+ * End-of-stream is a special token that signifies no more tokens
+ * are in the stream.
+ * @const
+ */ var end_of_stream = -1;
+
+ /**
+ * A stream represents an ordered sequence of tokens.
+ *
+ * @constructor
+ * @param {!(Array.<number>|Uint8Array)} tokens Array of tokens that provide the
+ * stream.
+ */
+ function Stream(tokens) {
+ /** @type {!Array.<number>} */
+ this.tokens = [].slice.call(tokens);
+ }
+
+ Stream.prototype = {
+ /**
+ * @return {boolean} True if end-of-stream has been hit.
+ */
+ endOfStream: function() {
+ return !this.tokens.length;
+ },
+
+ /**
+ * When a token is read from a stream, the first token in the
+ * stream must be returned and subsequently removed, and
+ * end-of-stream must be returned otherwise.
+ *
+ * @return {number} Get the next token from the stream, or
+ * end_of_stream.
+ */
+ read: function() {
+ if (!this.tokens.length)
+ return end_of_stream;
+ return this.tokens.shift();
+ },
+
+ /**
+ * When one or more tokens are prepended to a stream, those tokens
+ * must be inserted, in given order, before the first token in the
+ * stream.
+ *
+ * @param {(number|!Array.<number>)} token The token(s) to prepend to the stream.
+ */
+ prepend: function(token) {
+ if (Array.isArray(token)) {
+ var tokens = /**@type {!Array.<number>}*/(token);
+ while (tokens.length)
+ this.tokens.unshift(tokens.pop());
+ } else {
+ this.tokens.unshift(token);
+ }
+ },
+
+ /**
+ * When one or more tokens are pushed to a stream, those tokens
+ * must be inserted, in given order, after the last token in the
+ * stream.
+ *
+ * @param {(number|!Array.<number>)} token The tokens(s) to prepend to the stream.
+ */
+ push: function(token) {
+ if (Array.isArray(token)) {
+ var tokens = /**@type {!Array.<number>}*/(token);
+ while (tokens.length)
+ this.tokens.push(tokens.shift());
+ } else {
+ this.tokens.push(token);
+ }
+ }
+ };
+
+ //
+ // 4. Encodings
+ //
+
+ // 4.1 Encoders and decoders
+
+ /** @const */
+ var finished = -1;
+
+ /**
+ * @param {boolean} fatal If true, decoding errors raise an exception.
+ * @param {number=} opt_code_point Override the standard fallback code point.
+ * @return {number} The code point to insert on a decoding error.
+ */
+ function decoderError(fatal, opt_code_point) {
+ if (fatal)
+ throw TypeError('Decoder error');
+ return opt_code_point || 0xFFFD;
+ }
+
+ /**
+ * @param {number} code_point The code point that could not be encoded.
+ * @return {number} Always throws, no value is actually returned.
+ */
+ function encoderError(code_point) {
+ throw TypeError('The code point ' + code_point + ' could not be encoded.');
+ }
+
+ /** @interface */
+ function Decoder() {}
+ Decoder.prototype = {
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point, or |finished|.
+ */
+ handler: function(stream, bite) {}
+ };
+
+ /** @interface */
+ function Encoder() {}
+ Encoder.prototype = {
+ /**
+ * @param {Stream} stream The stream of code points being encoded.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit, or |finished|.
+ */
+ handler: function(stream, code_point) {}
+ };
+
+ // 4.2 Names and labels
+
+ // TODO: Define @typedef for Encoding: {name:string,labels:Array.<string>}
+ // https://github.com/google/closure-compiler/issues/247
+
+ /**
+ * @param {string} label The encoding label.
+ * @return {?{name:string,labels:Array.<string>}}
+ */
+ function getEncoding(label) {
+ // 1. Remove any leading and trailing ASCII whitespace from label.
+ label = String(label).trim().toLowerCase();
+
+ // 2. If label is an ASCII case-insensitive match for any of the
+ // labels listed in the table below, return the corresponding
+ // encoding, and failure otherwise.
+ if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) {
+ return label_to_encoding[label];
+ }
+ return null;
+ }
+
+ /**
+ * Encodings table: https://encoding.spec.whatwg.org/encodings.json
+ * @const
+ * @type {!Array.<{
+ * heading: string,
+ * encodings: Array.<{name:string,labels:Array.<string>}>
+ * }>}
+ */
+ var encodings = [
+ {
+ "encodings": [
+ {
+ "labels": [
+ "unicode-1-1-utf-8",
+ "utf-8",
+ "utf8"
+ ],
+ "name": "utf-8"
+ }
+ ],
+ "heading": "The Encoding"
+ },
+ {
+ "encodings": [
+ {
+ "labels": [
+ "866",
+ "cp866",
+ "csibm866",
+ "ibm866"
+ ],
+ "name": "ibm866"
+ },
+ {
+ "labels": [
+ "csisolatin2",
+ "iso-8859-2",
+ "iso-ir-101",
+ "iso8859-2",
+ "iso88592",
+ "iso_8859-2",
+ "iso_8859-2:1987",
+ "l2",
+ "latin2"
+ ],
+ "name": "iso-8859-2"
+ },
+ {
+ "labels": [
+ "csisolatin3",
+ "iso-8859-3",
+ "iso-ir-109",
+ "iso8859-3",
+ "iso88593",
+ "iso_8859-3",
+ "iso_8859-3:1988",
+ "l3",
+ "latin3"
+ ],
+ "name": "iso-8859-3"
+ },
+ {
+ "labels": [
+ "csisolatin4",
+ "iso-8859-4",
+ "iso-ir-110",
+ "iso8859-4",
+ "iso88594",
+ "iso_8859-4",
+ "iso_8859-4:1988",
+ "l4",
+ "latin4"
+ ],
+ "name": "iso-8859-4"
+ },
+ {
+ "labels": [
+ "csisolatincyrillic",
+ "cyrillic",
+ "iso-8859-5",
+ "iso-ir-144",
+ "iso8859-5",
+ "iso88595",
+ "iso_8859-5",
+ "iso_8859-5:1988"
+ ],
+ "name": "iso-8859-5"
+ },
+ {
+ "labels": [
+ "arabic",
+ "asmo-708",
+ "csiso88596e",
+ "csiso88596i",
+ "csisolatinarabic",
+ "ecma-114",
+ "iso-8859-6",
+ "iso-8859-6-e",
+ "iso-8859-6-i",
+ "iso-ir-127",
+ "iso8859-6",
+ "iso88596",
+ "iso_8859-6",
+ "iso_8859-6:1987"
+ ],
+ "name": "iso-8859-6"
+ },
+ {
+ "labels": [
+ "csisolatingreek",
+ "ecma-118",
+ "elot_928",
+ "greek",
+ "greek8",
+ "iso-8859-7",
+ "iso-ir-126",
+ "iso8859-7",
+ "iso88597",
+ "iso_8859-7",
+ "iso_8859-7:1987",
+ "sun_eu_greek"
+ ],
+ "name": "iso-8859-7"
+ },
+ {
+ "labels": [
+ "csiso88598e",
+ "csisolatinhebrew",
+ "hebrew",
+ "iso-8859-8",
+ "iso-8859-8-e",
+ "iso-ir-138",
+ "iso8859-8",
+ "iso88598",
+ "iso_8859-8",
+ "iso_8859-8:1988",
+ "visual"
+ ],
+ "name": "iso-8859-8"
+ },
+ {
+ "labels": [
+ "csiso88598i",
+ "iso-8859-8-i",
+ "logical"
+ ],
+ "name": "iso-8859-8-i"
+ },
+ {
+ "labels": [
+ "csisolatin6",
+ "iso-8859-10",
+ "iso-ir-157",
+ "iso8859-10",
+ "iso885910",
+ "l6",
+ "latin6"
+ ],
+ "name": "iso-8859-10"
+ },
+ {
+ "labels": [
+ "iso-8859-13",
+ "iso8859-13",
+ "iso885913"
+ ],
+ "name": "iso-8859-13"
+ },
+ {
+ "labels": [
+ "iso-8859-14",
+ "iso8859-14",
+ "iso885914"
+ ],
+ "name": "iso-8859-14"
+ },
+ {
+ "labels": [
+ "csisolatin9",
+ "iso-8859-15",
+ "iso8859-15",
+ "iso885915",
+ "iso_8859-15",
+ "l9"
+ ],
+ "name": "iso-8859-15"
+ },
+ {
+ "labels": [
+ "iso-8859-16"
+ ],
+ "name": "iso-8859-16"
+ },
+ {
+ "labels": [
+ "cskoi8r",
+ "koi",
+ "koi8",
+ "koi8-r",
+ "koi8_r"
+ ],
+ "name": "koi8-r"
+ },
+ {
+ "labels": [
+ "koi8-ru",
+ "koi8-u"
+ ],
+ "name": "koi8-u"
+ },
+ {
+ "labels": [
+ "csmacintosh",
+ "mac",
+ "macintosh",
+ "x-mac-roman"
+ ],
+ "name": "macintosh"
+ },
+ {
+ "labels": [
+ "dos-874",
+ "iso-8859-11",
+ "iso8859-11",
+ "iso885911",
+ "tis-620",
+ "windows-874"
+ ],
+ "name": "windows-874"
+ },
+ {
+ "labels": [
+ "cp1250",
+ "windows-1250",
+ "x-cp1250"
+ ],
+ "name": "windows-1250"
+ },
+ {
+ "labels": [
+ "cp1251",
+ "windows-1251",
+ "x-cp1251"
+ ],
+ "name": "windows-1251"
+ },
+ {
+ "labels": [
+ "ansi_x3.4-1968",
+ "ascii",
+ "cp1252",
+ "cp819",
+ "csisolatin1",
+ "ibm819",
+ "iso-8859-1",
+ "iso-ir-100",
+ "iso8859-1",
+ "iso88591",
+ "iso_8859-1",
+ "iso_8859-1:1987",
+ "l1",
+ "latin1",
+ "us-ascii",
+ "windows-1252",
+ "x-cp1252"
+ ],
+ "name": "windows-1252"
+ },
+ {
+ "labels": [
+ "cp1253",
+ "windows-1253",
+ "x-cp1253"
+ ],
+ "name": "windows-1253"
+ },
+ {
+ "labels": [
+ "cp1254",
+ "csisolatin5",
+ "iso-8859-9",
+ "iso-ir-148",
+ "iso8859-9",
+ "iso88599",
+ "iso_8859-9",
+ "iso_8859-9:1989",
+ "l5",
+ "latin5",
+ "windows-1254",
+ "x-cp1254"
+ ],
+ "name": "windows-1254"
+ },
+ {
+ "labels": [
+ "cp1255",
+ "windows-1255",
+ "x-cp1255"
+ ],
+ "name": "windows-1255"
+ },
+ {
+ "labels": [
+ "cp1256",
+ "windows-1256",
+ "x-cp1256"
+ ],
+ "name": "windows-1256"
+ },
+ {
+ "labels": [
+ "cp1257",
+ "windows-1257",
+ "x-cp1257"
+ ],
+ "name": "windows-1257"
+ },
+ {
+ "labels": [
+ "cp1258",
+ "windows-1258",
+ "x-cp1258"
+ ],
+ "name": "windows-1258"
+ },
+ {
+ "labels": [
+ "x-mac-cyrillic",
+ "x-mac-ukrainian"
+ ],
+ "name": "x-mac-cyrillic"
+ }
+ ],
+ "heading": "Legacy single-byte encodings"
+ },
+ {
+ "encodings": [
+ {
+ "labels": [
+ "chinese",
+ "csgb2312",
+ "csiso58gb231280",
+ "gb2312",
+ "gb_2312",
+ "gb_2312-80",
+ "gbk",
+ "iso-ir-58",
+ "x-gbk"
+ ],
+ "name": "gbk"
+ },
+ {
+ "labels": [
+ "gb18030"
+ ],
+ "name": "gb18030"
+ }
+ ],
+ "heading": "Legacy multi-byte Chinese (simplified) encodings"
+ },
+ {
+ "encodings": [
+ {
+ "labels": [
+ "big5",
+ "big5-hkscs",
+ "cn-big5",
+ "csbig5",
+ "x-x-big5"
+ ],
+ "name": "big5"
+ }
+ ],
+ "heading": "Legacy multi-byte Chinese (traditional) encodings"
+ },
+ {
+ "encodings": [
+ {
+ "labels": [
+ "cseucpkdfmtjapanese",
+ "euc-jp",
+ "x-euc-jp"
+ ],
+ "name": "euc-jp"
+ },
+ {
+ "labels": [
+ "csiso2022jp",
+ "iso-2022-jp"
+ ],
+ "name": "iso-2022-jp"
+ },
+ {
+ "labels": [
+ "csshiftjis",
+ "ms932",
+ "ms_kanji",
+ "shift-jis",
+ "shift_jis",
+ "sjis",
+ "windows-31j",
+ "x-sjis"
+ ],
+ "name": "shift_jis"
+ }
+ ],
+ "heading": "Legacy multi-byte Japanese encodings"
+ },
+ {
+ "encodings": [
+ {
+ "labels": [
+ "cseuckr",
+ "csksc56011987",
+ "euc-kr",
+ "iso-ir-149",
+ "korean",
+ "ks_c_5601-1987",
+ "ks_c_5601-1989",
+ "ksc5601",
+ "ksc_5601",
+ "windows-949"
+ ],
+ "name": "euc-kr"
+ }
+ ],
+ "heading": "Legacy multi-byte Korean encodings"
+ },
+ {
+ "encodings": [
+ {
+ "labels": [
+ "csiso2022kr",
+ "hz-gb-2312",
+ "iso-2022-cn",
+ "iso-2022-cn-ext",
+ "iso-2022-kr"
+ ],
+ "name": "replacement"
+ },
+ {
+ "labels": [
+ "utf-16be"
+ ],
+ "name": "utf-16be"
+ },
+ {
+ "labels": [
+ "utf-16",
+ "utf-16le"
+ ],
+ "name": "utf-16le"
+ },
+ {
+ "labels": [
+ "x-user-defined"
+ ],
+ "name": "x-user-defined"
+ }
+ ],
+ "heading": "Legacy miscellaneous encodings"
+ }
+ ];
+
+ // Label to encoding registry.
+ /** @type {Object.<string,{name:string,labels:Array.<string>}>} */
+ var label_to_encoding = {};
+ encodings.forEach(function(category) {
+ category.encodings.forEach(function(encoding) {
+ encoding.labels.forEach(function(label) {
+ label_to_encoding[label] = encoding;
+ });
+ });
+ });
+
+ // Registry of of encoder/decoder factories, by encoding name.
+ /** @type {Object.<string, function({fatal:boolean}): Encoder>} */
+ var encoders = {};
+ /** @type {Object.<string, function({fatal:boolean}): Decoder>} */
+ var decoders = {};
+
+ //
+ // 5. Indexes
+ //
+
+ /**
+ * @param {number} pointer The |pointer| to search for.
+ * @param {(!Array.<?number>|undefined)} index The |index| to search within.
+ * @return {?number} The code point corresponding to |pointer| in |index|,
+ * or null if |code point| is not in |index|.
+ */
+ function indexCodePointFor(pointer, index) {
+ if (!index) return null;
+ return index[pointer] || null;
+ }
+
+ /**
+ * @param {number} code_point The |code point| to search for.
+ * @param {!Array.<?number>} index The |index| to search within.
+ * @return {?number} The first pointer corresponding to |code point| in
+ * |index|, or null if |code point| is not in |index|.
+ */
+ function indexPointerFor(code_point, index) {
+ var pointer = index.indexOf(code_point);
+ return pointer === -1 ? null : pointer;
+ }
+
+ /**
+ * @param {string} name Name of the index.
+ * @return {(!Array.<number>|!Array.<Array.<number>>)}
+ * */
+ function index(name) {
+ if (!('encoding-indexes' in global)) {
+ throw Error("Indexes missing." +
+ " Did you forget to include encoding-indexes.js?");
+ }
+ return global['encoding-indexes'][name];
+ }
+
+ /**
+ * @param {number} pointer The |pointer| to search for in the gb18030 index.
+ * @return {?number} The code point corresponding to |pointer| in |index|,
+ * or null if |code point| is not in the gb18030 index.
+ */
+ function indexGB18030RangesCodePointFor(pointer) {
+ // 1. If pointer is greater than 39419 and less than 189000, or
+ // pointer is greater than 1237575, return null.
+ if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575))
+ return null;
+
+ // 2. Let offset be the last pointer in index gb18030 ranges that
+ // is equal to or less than pointer and let code point offset be
+ // its corresponding code point.
+ var offset = 0;
+ var code_point_offset = 0;
+ var idx = index('gb18030');
+ var i;
+ for (i = 0; i < idx.length; ++i) {
+ /** @type {!Array.<number>} */
+ var entry = idx[i];
+ if (entry[0] <= pointer) {
+ offset = entry[0];
+ code_point_offset = entry[1];
+ } else {
+ break;
+ }
+ }
+
+ // 3. Return a code point whose value is code point offset +
+ // pointer − offset.
+ return code_point_offset + pointer - offset;
+ }
+
+ /**
+ * @param {number} code_point The |code point| to locate in the gb18030 index.
+ * @return {number} The first pointer corresponding to |code point| in the
+ * gb18030 index.
+ */
+ function indexGB18030RangesPointerFor(code_point) {
+ // 1. Let offset be the last code point in index gb18030 ranges
+ // that is equal to or less than code point and let pointer offset
+ // be its corresponding pointer.
+ var offset = 0;
+ var pointer_offset = 0;
+ var idx = index('gb18030');
+ var i;
+ for (i = 0; i < idx.length; ++i) {
+ /** @type {!Array.<number>} */
+ var entry = idx[i];
+ if (entry[1] <= code_point) {
+ offset = entry[1];
+ pointer_offset = entry[0];
+ } else {
+ break;
+ }
+ }
+
+ // 2. Return a pointer whose value is pointer offset + code point
+ // − offset.
+ return pointer_offset + code_point - offset;
+ }
+
+ /**
+ * @param {number} code_point The |code_point| to search for in the shift_jis index.
+ * @return {?number} The code point corresponding to |pointer| in |index|,
+ * or null if |code point| is not in the shift_jis index.
+ */
+ function indexShiftJISPointerFor(code_point) {
+ // 1. Let index be index jis0208 excluding all pointers in the
+ // range 8272 to 8835.
+ var pointer = indexPointerFor(code_point, index('jis0208'));
+ if (pointer === null || inRange(pointer, 8272, 8835))
+ return null;
+
+ // 2. Return the index pointer for code point in index.
+ return pointer;
+ }
+
+ /**
+ * @param {number} code_point The |code_point| to search for in the big5 index.
+ * @return {?number} The code point corresponding to |pointer| in |index|,
+ * or null if |code point| is not in the big5 index.
+ */
+ function indexBig5PointerFor(code_point) {
+
+ // 1. Let index be index big5.
+ var index_ = index('big5');
+
+ // 2. If code point is U+2550, U+255E, U+2561, U+256A, U+5341, or
+ // U+5345, return the last pointer corresponding to code point in
+ // index.
+ if (code_point === 0x2550 || code_point === 0x255E ||
+ code_point === 0x2561 || code_point === 0x256A ||
+ code_point === 0x5341 || code_point === 0x5345) {
+ return index.lastIndexOf(code_point);
+ }
+
+ // 3. Return the index pointer for code point in index.
+ return indexPointerFor(code_point, index_);
+ }
+
+ //
+ // 7. API
+ //
+
+ /** @const */ var DEFAULT_ENCODING = 'utf-8';
+
+ // 7.1 Interface TextDecoder
+
+ /**
+ * @constructor
+ * @param {string=} encoding The label of the encoding;
+ * defaults to 'utf-8'.
+ * @param {Object=} options
+ */
+ function TextDecoder(encoding, options) {
+ if (!(this instanceof TextDecoder)) {
+ return new TextDecoder(encoding, options);
+ }
+ encoding = encoding !== undefined ? String(encoding) : DEFAULT_ENCODING;
+ options = ToDictionary(options);
+ /** @private */
+ this._encoding = getEncoding(encoding);
+ if (this._encoding === null || this._encoding.name === 'replacement')
+ throw RangeError('Unknown encoding: ' + encoding);
+
+ if (!decoders[this._encoding.name]) {
+ throw Error('Decoder not present.' +
+ ' Did you forget to include encoding-indexes.js?');
+ }
+
+ /** @private @type {boolean} */
+ this._streaming = false;
+ /** @private @type {boolean} */
+ this._BOMseen = false;
+ /** @private @type {?Decoder} */
+ this._decoder = null;
+ /** @private @type {boolean} */
+ this._fatal = Boolean(options['fatal']);
+ /** @private @type {boolean} */
+ this._ignoreBOM = Boolean(options['ignoreBOM']);
+
+ if (Object.defineProperty) {
+ Object.defineProperty(this, 'encoding', {value: this._encoding.name});
+ Object.defineProperty(this, 'fatal', {value: this._fatal});
+ Object.defineProperty(this, 'ignoreBOM', {value: this._ignoreBOM});
+ } else {
+ this.encoding = this._encoding.name;
+ this.fatal = this._fatal;
+ this.ignoreBOM = this._ignoreBOM;
+ }
+
+ return this;
+ }
+
+ TextDecoder.prototype = {
+ /**
+ * @param {ArrayBufferView=} input The buffer of bytes to decode.
+ * @param {Object=} options
+ * @return {string} The decoded string.
+ */
+ decode: function decode(input, options) {
+ var bytes;
+ if (typeof input === 'object' && input instanceof ArrayBuffer) {
+ bytes = new Uint8Array(input);
+ } else if (typeof input === 'object' && 'buffer' in input &&
+ input.buffer instanceof ArrayBuffer) {
+ bytes = new Uint8Array(input.buffer,
+ input.byteOffset,
+ input.byteLength);
+ } else {
+ bytes = new Uint8Array(0);
+ }
+
+ options = ToDictionary(options);
+
+ if (!this._streaming) {
+ this._decoder = decoders[this._encoding.name]({fatal: this._fatal});
+ this._BOMseen = false;
+ }
+ this._streaming = Boolean(options['stream']);
+
+ var input_stream = new Stream(bytes);
+
+ var code_points = [];
+
+ /** @type {?(number|!Array.<number>)} */
+ var result;
+
+ while (!input_stream.endOfStream()) {
+ result = this._decoder.handler(input_stream, input_stream.read());
+ if (result === finished)
+ break;
+ if (result === null)
+ continue;
+ if (Array.isArray(result))
+ code_points.push.apply(code_points, /**@type {!Array.<number>}*/(result));
+ else
+ code_points.push(result);
+ }
+ if (!this._streaming) {
+ do {
+ result = this._decoder.handler(input_stream, input_stream.read());
+ if (result === finished)
+ break;
+ if (result === null)
+ continue;
+ if (Array.isArray(result))
+ code_points.push.apply(code_points, /**@type {!Array.<number>}*/(result));
+ else
+ code_points.push(result);
+ } while (!input_stream.endOfStream());
+ this._decoder = null;
+ }
+
+ if (code_points.length) {
+ // If encoding is one of utf-8, utf-16be, and utf-16le, and
+ // ignore BOM flag and BOM seen flag are unset, run these
+ // subsubsteps:
+ if (['utf-8', 'utf-16le', 'utf-16be'].indexOf(this.encoding) !== -1 &&
+ !this._ignoreBOM && !this._BOMseen) {
+ // If token is U+FEFF, set BOM seen flag.
+ if (code_points[0] === 0xFEFF) {
+ this._BOMseen = true;
+ code_points.shift();
+ } else {
+ // Otherwise, if token is not end-of-stream, set BOM seen
+ // flag and append token to output.
+ this._BOMseen = true;
+ }
+ }
+ }
+
+ return codePointsToString(code_points);
+ }
+ };
+
+ // 7.2 Interface TextEncoder
+
+ /**
+ * @constructor
+ * @param {string=} encoding The label of the encoding;
+ * defaults to 'utf-8'.
+ * @param {Object=} options
+ */
+ function TextEncoder(encoding, options) {
+ if (!(this instanceof TextEncoder))
+ return new TextEncoder(encoding, options);
+ encoding = encoding !== undefined ? String(encoding) : DEFAULT_ENCODING;
+ options = ToDictionary(options);
+ /** @private */
+ this._encoding = getEncoding(encoding);
+ if (this._encoding === null || this._encoding.name === 'replacement')
+ throw RangeError('Unknown encoding: ' + encoding);
+
+ var allowLegacyEncoding =
+ Boolean(options['NONSTANDARD_allowLegacyEncoding']);
+ var isLegacyEncoding = (this._encoding.name !== 'utf-8' &&
+ this._encoding.name !== 'utf-16le' &&
+ this._encoding.name !== 'utf-16be');
+ if (this._encoding === null || (isLegacyEncoding && !allowLegacyEncoding))
+ throw RangeError('Unknown encoding: ' + encoding);
+
+ if (!encoders[this._encoding.name]) {
+ throw Error('Encoder not present.' +
+ ' Did you forget to include encoding-indexes.js?');
+ }
+
+ /** @private @type {boolean} */
+ this._streaming = false;
+ /** @private @type {?Encoder} */
+ this._encoder = null;
+ /** @private @type {{fatal: boolean}} */
+ this._options = {fatal: Boolean(options['fatal'])};
+
+ if (Object.defineProperty)
+ Object.defineProperty(this, 'encoding', {value: this._encoding.name});
+ else
+ this.encoding = this._encoding.name;
+
+ return this;
+ }
+
+ TextEncoder.prototype = {
+ /**
+ * @param {string=} opt_string The string to encode.
+ * @param {Object=} options
+ * @return {Uint8Array} Encoded bytes, as a Uint8Array.
+ */
+ encode: function encode(opt_string, options) {
+ opt_string = opt_string ? String(opt_string) : '';
+ options = ToDictionary(options);
+
+ // NOTE: This option is nonstandard. None of the encodings
+ // permitted for encoding (i.e. UTF-8, UTF-16) are stateful,
+ // so streaming is not necessary.
+ if (!this._streaming)
+ this._encoder = encoders[this._encoding.name](this._options);
+ this._streaming = Boolean(options['stream']);
+
+ var bytes = [];
+ var input_stream = new Stream(stringToCodePoints(opt_string));
+ /** @type {?(number|!Array.<number>)} */
+ var result;
+ while (!input_stream.endOfStream()) {
+ result = this._encoder.handler(input_stream, input_stream.read());
+ if (result === finished)
+ break;
+ if (Array.isArray(result))
+ bytes.push.apply(bytes, /**@type {!Array.<number>}*/(result));
+ else
+ bytes.push(result);
+ }
+ if (!this._streaming) {
+ while (true) {
+ result = this._encoder.handler(input_stream, input_stream.read());
+ if (result === finished)
+ break;
+ if (Array.isArray(result))
+ bytes.push.apply(bytes, /**@type {!Array.<number>}*/(result));
+ else
+ bytes.push(result);
+ }
+ this._encoder = null;
+ }
+ return new Uint8Array(bytes);
+ }
+ };
+
+
+ //
+ // 8. The encoding
+ //
+
+ // 8.1 utf-8
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function UTF8Decoder(options) {
+ var fatal = options.fatal;
+
+ // utf-8's decoder's has an associated utf-8 code point, utf-8
+ // bytes seen, and utf-8 bytes needed (all initially 0), a utf-8
+ // lower boundary (initially 0x80), and a utf-8 upper boundary
+ // (initially 0xBF).
+ var /** @type {number} */ utf8_code_point = 0,
+ /** @type {number} */ utf8_bytes_seen = 0,
+ /** @type {number} */ utf8_bytes_needed = 0,
+ /** @type {number} */ utf8_lower_boundary = 0x80,
+ /** @type {number} */ utf8_upper_boundary = 0xBF;
+
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream and utf-8 bytes needed is not 0,
+ // set utf-8 bytes needed to 0 and return error.
+ if (bite === end_of_stream && utf8_bytes_needed !== 0) {
+ utf8_bytes_needed = 0;
+ return decoderError(fatal);
+ }
+
+ // 2. If byte is end-of-stream, return finished.
+ if (bite === end_of_stream)
+ return finished;
+
+ // 3. If utf-8 bytes needed is 0, based on byte:
+ if (utf8_bytes_needed === 0) {
+
+ // 0x00 to 0x7F
+ if (inRange(bite, 0x00, 0x7F)) {
+ // Return a code point whose value is byte.
+ return bite;
+ }
+
+ // 0xC2 to 0xDF
+ if (inRange(bite, 0xC2, 0xDF)) {
+ // Set utf-8 bytes needed to 1 and utf-8 code point to byte
+ // − 0xC0.
+ utf8_bytes_needed = 1;
+ utf8_code_point = bite - 0xC0;
+ }
+
+ // 0xE0 to 0xEF
+ else if (inRange(bite, 0xE0, 0xEF)) {
+ // 1. If byte is 0xE0, set utf-8 lower boundary to 0xA0.
+ if (bite === 0xE0)
+ utf8_lower_boundary = 0xA0;
+ // 2. If byte is 0xED, set utf-8 upper boundary to 0x9F.
+ if (bite === 0xED)
+ utf8_upper_boundary = 0x9F;
+ // 3. Set utf-8 bytes needed to 2 and utf-8 code point to
+ // byte − 0xE0.
+ utf8_bytes_needed = 2;
+ utf8_code_point = bite - 0xE0;
+ }
+
+ // 0xF0 to 0xF4
+ else if (inRange(bite, 0xF0, 0xF4)) {
+ // 1. If byte is 0xF0, set utf-8 lower boundary to 0x90.
+ if (bite === 0xF0)
+ utf8_lower_boundary = 0x90;
+ // 2. If byte is 0xF4, set utf-8 upper boundary to 0x8F.
+ if (bite === 0xF4)
+ utf8_upper_boundary = 0x8F;
+ // 3. Set utf-8 bytes needed to 3 and utf-8 code point to
+ // byte − 0xF0.
+ utf8_bytes_needed = 3;
+ utf8_code_point = bite - 0xF0;
+ }
+
+ // Otherwise
+ else {
+ // Return error.
+ return decoderError(fatal);
+ }
+
+ // Then (byte is in the range 0xC2 to 0xF4) set utf-8 code
+ // point to utf-8 code point << (6 × utf-8 bytes needed) and
+ // return continue.
+ utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed);
+ return null;
+ }
+
+ // 4. If byte is not in the range utf-8 lower boundary to utf-8
+ // upper boundary, run these substeps:
+ if (!inRange(bite, utf8_lower_boundary, utf8_upper_boundary)) {
+
+ // 1. Set utf-8 code point, utf-8 bytes needed, and utf-8
+ // bytes seen to 0, set utf-8 lower boundary to 0x80, and set
+ // utf-8 upper boundary to 0xBF.
+ utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0;
+ utf8_lower_boundary = 0x80;
+ utf8_upper_boundary = 0xBF;
+
+ // 2. Prepend byte to stream.
+ stream.prepend(bite);
+
+ // 3. Return error.
+ return decoderError(fatal);
+ }
+
+ // 5. Set utf-8 lower boundary to 0x80 and utf-8 upper boundary
+ // to 0xBF.
+ utf8_lower_boundary = 0x80;
+ utf8_upper_boundary = 0xBF;
+
+ // 6. Increase utf-8 bytes seen by one and set utf-8 code point
+ // to utf-8 code point + (byte − 0x80) << (6 × (utf-8 bytes
+ // needed − utf-8 bytes seen)).
+ utf8_bytes_seen += 1;
+ utf8_code_point += (bite - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen));
+
+ // 7. If utf-8 bytes seen is not equal to utf-8 bytes needed,
+ // continue.
+ if (utf8_bytes_seen !== utf8_bytes_needed)
+ return null;
+
+ // 8. Let code point be utf-8 code point.
+ var code_point = utf8_code_point;
+
+ // 9. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes
+ // seen to 0.
+ utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0;
+
+ // 10. Return a code point whose value is code point.
+ return code_point;
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ */
+ function UTF8Encoder(options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007f))
+ return code_point;
+
+ // 3. Set count and offset based on the range code point is in:
+ var count, offset;
+ // U+0080 to U+07FF: 1 and 0xC0
+ if (inRange(code_point, 0x0080, 0x07FF)) {
+ count = 1;
+ offset = 0xC0;
+ }
+ // U+0800 to U+FFFF: 2 and 0xE0
+ else if (inRange(code_point, 0x0800, 0xFFFF)) {
+ count = 2;
+ offset = 0xE0;
+ }
+ // U+10000 to U+10FFFF: 3 and 0xF0
+ else if (inRange(code_point, 0x10000, 0x10FFFF)) {
+ count = 3;
+ offset = 0xF0;
+ }
+
+ // 4.Let bytes be a byte sequence whose first byte is (code
+ // point >> (6 × count)) + offset.
+ var bytes = [(code_point >> (6 * count)) + offset];
+
+ // 5. Run these substeps while count is greater than 0:
+ while (count > 0) {
+
+ // 1. Set temp to code point >> (6 × (count − 1)).
+ var temp = code_point >> (6 * (count - 1));
+
+ // 2. Append to bytes 0x80 | (temp & 0x3F).
+ bytes.push(0x80 | (temp & 0x3F));
+
+ // 3. Decrease count by one.
+ count -= 1;
+ }
+
+ // 6. Return bytes bytes, in order.
+ return bytes;
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['utf-8'] = function(options) {
+ return new UTF8Encoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['utf-8'] = function(options) {
+ return new UTF8Decoder(options);
+ };
+
+ //
+ // 9. Legacy single-byte encodings
+ //
+
+ // 9.1 single-byte decoder
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {!Array.<number>} index The encoding index.
+ * @param {{fatal: boolean}} options
+ */
+ function SingleByteDecoder(index, options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream, return finished.
+ if (bite === end_of_stream)
+ return finished;
+
+ // 2. If byte is in the range 0x00 to 0x7F, return a code point
+ // whose value is byte.
+ if (inRange(bite, 0x00, 0x7F))
+ return bite;
+
+ // 3. Let code point be the index code point for byte − 0x80 in
+ // index single-byte.
+ var code_point = index[bite - 0x80];
+
+ // 4. If code point is null, return error.
+ if (code_point === null)
+ return decoderError(fatal);
+
+ // 5. Return a code point whose value is code point.
+ return code_point;
+ };
+ }
+
+ // 9.2 single-byte encoder
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {!Array.<?number>} index The encoding index.
+ * @param {{fatal: boolean}} options
+ */
+ function SingleByteEncoder(index, options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007F))
+ return code_point;
+
+ // 3. Let pointer be the index pointer for code point in index
+ // single-byte.
+ var pointer = indexPointerFor(code_point, index);
+
+ // 4. If pointer is null, return error with code point.
+ if (pointer === null)
+ encoderError(code_point);
+
+ // 5. Return a byte whose value is pointer + 0x80.
+ return pointer + 0x80;
+ };
+ }
+
+ (function() {
+ if (!('encoding-indexes' in global))
+ return;
+ encodings.forEach(function(category) {
+ if (category.heading !== 'Legacy single-byte encodings')
+ return;
+ category.encodings.forEach(function(encoding) {
+ var name = encoding.name;
+ var idx = index(name);
+ /** @param {{fatal: boolean}} options */
+ decoders[name] = function(options) {
+ return new SingleByteDecoder(idx, options);
+ };
+ /** @param {{fatal: boolean}} options */
+ encoders[name] = function(options) {
+ return new SingleByteEncoder(idx, options);
+ };
+ });
+ });
+ }());
+
+ //
+ // 10. Legacy multi-byte Chinese (simplified) encodings
+ //
+
+ // 10.1 gbk
+
+ // 10.1.1 gbk decoder
+ // gbk's decoder is gb18030's decoder.
+ /** @param {{fatal: boolean}} options */
+ decoders['gbk'] = function(options) {
+ return new GB18030Decoder(options);
+ };
+
+ // 10.1.2 gbk encoder
+ // gbk's encoder is gb18030's encoder with its gbk flag set.
+ /** @param {{fatal: boolean}} options */
+ encoders['gbk'] = function(options) {
+ return new GB18030Encoder(options, true);
+ };
+
+ // 10.2 gb18030
+
+ // 10.2.1 gb18030 decoder
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function GB18030Decoder(options) {
+ var fatal = options.fatal;
+ // gb18030's decoder has an associated gb18030 first, gb18030
+ // second, and gb18030 third (all initially 0x00).
+ var /** @type {number} */ gb18030_first = 0x00,
+ /** @type {number} */ gb18030_second = 0x00,
+ /** @type {number} */ gb18030_third = 0x00;
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream and gb18030 first, gb18030
+ // second, and gb18030 third are 0x00, return finished.
+ if (bite === end_of_stream && gb18030_first === 0x00 &&
+ gb18030_second === 0x00 && gb18030_third === 0x00) {
+ return finished;
+ }
+ // 2. If byte is end-of-stream, and gb18030 first, gb18030
+ // second, or gb18030 third is not 0x00, set gb18030 first,
+ // gb18030 second, and gb18030 third to 0x00, and return error.
+ if (bite === end_of_stream &&
+ (gb18030_first !== 0x00 || gb18030_second !== 0x00 || gb18030_third !== 0x00)) {
+ gb18030_first = 0x00;
+ gb18030_second = 0x00;
+ gb18030_third = 0x00;
+ decoderError(fatal);
+ }
+ var code_point;
+ // 3. If gb18030 third is not 0x00, run these substeps:
+ if (gb18030_third !== 0x00) {
+ // 1. Let code point be null.
+ code_point = null;
+ // 2. If byte is in the range 0x30 to 0x39, set code point to
+ // the index gb18030 ranges code point for (((gb18030 first −
+ // 0x81) × 10 + gb18030 second − 0x30) × 126 + gb18030 third −
+ // 0x81) × 10 + byte − 0x30.
+ if (inRange(bite, 0x30, 0x39)) {
+ code_point = indexGB18030RangesCodePointFor(
+ (((gb18030_first - 0x81) * 10 + (gb18030_second - 0x30)) * 126 +
+ (gb18030_third - 0x81)) * 10 + bite - 0x30);
+ }
+
+ // 3. Let buffer be a byte sequence consisting of gb18030
+ // second, gb18030 third, and byte, in order.
+ var buffer = [gb18030_second, gb18030_third, bite];
+
+ // 4. Set gb18030 first, gb18030 second, and gb18030 third to
+ // 0x00.
+ gb18030_first = 0x00;
+ gb18030_second = 0x00;
+ gb18030_third = 0x00;
+
+ // 5. If code point is null, prepend buffer to stream and
+ // return error.
+ if (code_point === null) {
+ stream.prepend(buffer);
+ return decoderError(fatal);
+ }
+
+ // 6. Return a code point whose value is code point.
+ return code_point;
+ }
+
+ // 4. If gb18030 second is not 0x00, run these substeps:
+ if (gb18030_second !== 0x00) {
+
+ // 1. If byte is in the range 0x81 to 0xFE, set gb18030 third
+ // to byte and return continue.
+ if (inRange(bite, 0x81, 0xFE)) {
+ gb18030_third = bite;
+ return null;
+ }
+
+ // 2. Prepend gb18030 second followed by byte to stream, set
+ // gb18030 first and gb18030 second to 0x00, and return error.
+ stream.prepend([gb18030_second, bite]);
+ gb18030_first = 0x00;
+ gb18030_second = 0x00;
+ return decoderError(fatal);
+ }
+
+ // 5. If gb18030 first is not 0x00, run these substeps:
+ if (gb18030_first !== 0x00) {
+
+ // 1. If byte is in the range 0x30 to 0x39, set gb18030 second
+ // to byte and return continue.
+ if (inRange(bite, 0x30, 0x39)) {
+ gb18030_second = bite;
+ return null;
+ }
+
+ // 2. Let lead be gb18030 first, let pointer be null, and set
+ // gb18030 first to 0x00.
+ var lead = gb18030_first;
+ var pointer = null;
+ gb18030_first = 0x00;
+
+ // 3. Let offset be 0x40 if byte is less than 0x7F and 0x41
+ // otherwise.
+ var offset = bite < 0x7F ? 0x40 : 0x41;
+
+ // 4. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFE,
+ // set pointer to (lead − 0x81) × 190 + (byte − offset).
+ if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE))
+ pointer = (lead - 0x81) * 190 + (bite - offset);
+
+ // 5. Let code point be null if pointer is null and the index
+ // code point for pointer in index gb18030 otherwise.
+ code_point = pointer === null ? null :
+ indexCodePointFor(pointer, index('gb18030'));
+
+ // 6. If code point is null and byte is in the range 0x00 to
+ // 0x7F, prepend byte to stream.
+ if (code_point === null && inRange(bite, 0x00, 0x7F))
+ stream.prepend(bite);
+
+ // 7. If code point is null, return error.
+ if (code_point === null)
+ return decoderError(fatal);
+
+ // 8. Return a code point whose value is code point.
+ return code_point;
+ }
+
+ // 6. If byte is in the range 0x00 to 0x7F, return a code point
+ // whose value is byte.
+ if (inRange(bite, 0x00, 0x7F))
+ return bite;
+
+ // 7. If byte is 0x80, return code point U+20AC.
+ if (bite === 0x80)
+ return 0x20AC;
+
+ // 8. If byte is in the range 0x81 to 0xFE, set gb18030 first to
+ // byte and return continue.
+ if (inRange(bite, 0x81, 0xFE)) {
+ gb18030_first = bite;
+ return null;
+ }
+
+ // 9. Return error.
+ return decoderError(fatal);
+ };
+ }
+
+ // 10.2.2 gb18030 encoder
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ * @param {boolean=} gbk_flag
+ */
+ function GB18030Encoder(options, gbk_flag) {
+ var fatal = options.fatal;
+ // gb18030's decoder has an associated gbk flag (initially unset).
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007F)) {
+ return code_point;
+ }
+
+ // 3. If the gbk flag is set and code point is U+20AC, return
+ // byte 0x80.
+ if (gbk_flag && code_point === 0x20AC)
+ return 0x80;
+
+ // 4. Let pointer be the index pointer for code point in index
+ // gb18030.
+ var pointer = indexPointerFor(code_point, index('gb18030'));
+
+ // 5. If pointer is not null, run these substeps:
+ if (pointer !== null) {
+
+ // 1. Let lead be pointer / 190 + 0x81.
+ var lead = div(pointer, 190) + 0x81;
+
+ // 2. Let trail be pointer % 190.
+ var trail = pointer % 190;
+
+ // 3. Let offset be 0x40 if trail is less than 0x3F and 0x41 otherwise.
+ var offset = trail < 0x3F ? 0x40 : 0x41;
+
+ // 4. Return two bytes whose values are lead and trail + offset.
+ return [lead, trail + offset];
+ }
+
+ // 6. If gbk flag is set, return error with code point.
+ if (gbk_flag)
+ return encoderError(code_point);
+
+ // 7. Set pointer to the index gb18030 ranges pointer for code
+ // point.
+ pointer = indexGB18030RangesPointerFor(code_point);
+
+ // 8. Let byte1 be pointer / 10 / 126 / 10.
+ var byte1 = div(div(div(pointer, 10), 126), 10);
+
+ // 9. Set pointer to pointer − byte1 × 10 × 126 × 10.
+ pointer = pointer - byte1 * 10 * 126 * 10;
+
+ // 10. Let byte2 be pointer / 10 / 126.
+ var byte2 = div(div(pointer, 10), 126);
+
+ // 11. Set pointer to pointer − byte2 × 10 × 126.
+ pointer = pointer - byte2 * 10 * 126;
+
+ // 12. Let byte3 be pointer / 10.
+ var byte3 = div(pointer, 10);
+
+ // 13. Let byte4 be pointer − byte3 × 10.
+ var byte4 = pointer - byte3 * 10;
+
+ // 14. Return four bytes whose values are byte1 + 0x81, byte2 +
+ // 0x30, byte3 + 0x81, byte4 + 0x30.
+ return [byte1 + 0x81,
+ byte2 + 0x30,
+ byte3 + 0x81,
+ byte4 + 0x30];
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['gb18030'] = function(options) {
+ return new GB18030Encoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['gb18030'] = function(options) {
+ return new GB18030Decoder(options);
+ };
+
+
+ //
+ // 11. Legacy multi-byte Chinese (traditional) encodings
+ //
+
+ // 11.1 big5
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function Big5Decoder(options) {
+ var fatal = options.fatal;
+ // big5's decoder has an associated big5 lead (initially 0x00).
+ var /** @type {number} */ big5_lead = 0x00;
+
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream and big5 lead is not 0x00, set
+ // big5 lead to 0x00 and return error.
+ if (bite === end_of_stream && big5_lead !== 0x00) {
+ big5_lead = 0x00;
+ return decoderError(fatal);
+ }
+
+ // 2. If byte is end-of-stream and big5 lead is 0x00, return
+ // finished.
+ if (bite === end_of_stream && big5_lead === 0x00)
+ return finished;
+
+ // 3. If big5 lead is not 0x00, let lead be big5 lead, let
+ // pointer be null, set big5 lead to 0x00, and then run these
+ // substeps:
+ if (big5_lead !== 0x00) {
+ var lead = big5_lead;
+ var pointer = null;
+ big5_lead = 0x00;
+
+ // 1. Let offset be 0x40 if byte is less than 0x7F and 0x62
+ // otherwise.
+ var offset = bite < 0x7F ? 0x40 : 0x62;
+
+ // 2. If byte is in the range 0x40 to 0x7E or 0xA1 to 0xFE,
+ // set pointer to (lead − 0x81) × 157 + (byte − offset).
+ if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE))
+ pointer = (lead - 0x81) * 157 + (bite - offset);
+
+ // 3. If there is a row in the table below whose first column
+ // is pointer, return the two code points listed in its second
+ // column
+ // Pointer | Code points
+ // --------+--------------
+ // 1133 | U+00CA U+0304
+ // 1135 | U+00CA U+030C
+ // 1164 | U+00EA U+0304
+ // 1166 | U+00EA U+030C
+ switch (pointer) {
+ case 1133: return [0x00CA, 0x0304];
+ case 1135: return [0x00CA, 0x030C];
+ case 1164: return [0x00EA, 0x0304];
+ case 1166: return [0x00EA, 0x030C];
+ }
+
+ // 4. Let code point be null if pointer is null and the index
+ // code point for pointer in index big5 otherwise.
+ var code_point = (pointer === null) ? null :
+ indexCodePointFor(pointer, index('big5'));
+
+ // 5. If code point is null and byte is in the range 0x00 to
+ // 0x7F, prepend byte to stream.
+ if (code_point === null && inRange(bite, 0x00, 0x7F))
+ stream.prepend(bite);
+
+ // 6. If code point is null, return error.
+ if (code_point === null)
+ return decoderError(fatal);
+
+ // 7. Return a code point whose value is code point.
+ return code_point;
+ }
+
+ // 4. If byte is in the range 0x00 to 0x7F, return a code point
+ // whose value is byte.
+ if (inRange(bite, 0x00, 0x7F))
+ return bite;
+
+ // 5. If byte is in the range 0x81 to 0xFE, set big5 lead to
+ // byte and return continue.
+ if (inRange(bite, 0x81, 0xFE)) {
+ big5_lead = bite;
+ return null;
+ }
+
+ // 6. Return error.
+ return decoderError(fatal);
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ */
+ function Big5Encoder(options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007F))
+ return code_point;
+
+ // 3. Let pointer be the index big5 pointer for code point.
+ var pointer = indexBig5PointerFor(code_point, index('big5'));
+
+ // 4. If pointer is null, return error with code point.
+ if (pointer === null)
+ return encoderError(code_point);
+
+ // 5. Let lead be pointer / 157 + 0x81.
+ var lead = div(pointer, 157) + 0x81;
+
+ // 6. If lead is less than 0xA1, return error with code point.
+ if (lead < 0xA1)
+ return encoderError(code_point);
+
+ // 7. Let trail be pointer % 157.
+ var trail = pointer % 157;
+
+ // 8. Let offset be 0x40 if trail is less than 0x3F and 0x62
+ // otherwise.
+ var offset = trail < 0x3F ? 0x40 : 0x62;
+
+ // Return two bytes whose values are lead and trail + offset.
+ return [lead, trail + offset];
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['big5'] = function(options) {
+ return new Big5Encoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['big5'] = function(options) {
+ return new Big5Decoder(options);
+ };
+
+
+ //
+ // 12. Legacy multi-byte Japanese encodings
+ //
+
+ // 12.1 euc-jp
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function EUCJPDecoder(options) {
+ var fatal = options.fatal;
+
+ // euc-jp's decoder has an associated euc-jp jis0212 flag
+ // (initially unset) and euc-jp lead (initially 0x00).
+ var /** @type {boolean} */ eucjp_jis0212_flag = false,
+ /** @type {number} */ eucjp_lead = 0x00;
+
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream and euc-jp lead is not 0x00, set
+ // euc-jp lead to 0x00, and return error.
+ if (bite === end_of_stream && eucjp_lead !== 0x00) {
+ eucjp_lead = 0x00;
+ return decoderError(fatal);
+ }
+
+ // 2. If byte is end-of-stream and euc-jp lead is 0x00, return
+ // finished.
+ if (bite === end_of_stream && eucjp_lead === 0x00)
+ return finished;
+
+ // 3. If euc-jp lead is 0x8E and byte is in the range 0xA1 to
+ // 0xDF, set euc-jp lead to 0x00 and return a code point whose
+ // value is 0xFF61 + byte − 0xA1.
+ if (eucjp_lead === 0x8E && inRange(bite, 0xA1, 0xDF)) {
+ eucjp_lead = 0x00;
+ return 0xFF61 + bite - 0xA1;
+ }
+
+ // 4. If euc-jp lead is 0x8F and byte is in the range 0xA1 to
+ // 0xFE, set the euc-jp jis0212 flag, set euc-jp lead to byte,
+ // and return continue.
+ if (eucjp_lead === 0x8F && inRange(bite, 0xA1, 0xFE)) {
+ eucjp_jis0212_flag = true;
+ eucjp_lead = bite;
+ return null;
+ }
+
+ // 5. If euc-jp lead is not 0x00, let lead be euc-jp lead, set
+ // euc-jp lead to 0x00, and run these substeps:
+ if (eucjp_lead !== 0x00) {
+ var lead = eucjp_lead;
+ eucjp_lead = 0x00;
+
+ // 1. Let code point be null.
+ var code_point = null;
+
+ // 2. If lead and byte are both in the range 0xA1 to 0xFE, set
+ // code point to the index code point for (lead − 0xA1) × 94 +
+ // byte − 0xA1 in index jis0208 if the euc-jp jis0212 flag is
+ // unset and in index jis0212 otherwise.
+ if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) {
+ code_point = indexCodePointFor(
+ (lead - 0xA1) * 94 + (bite - 0xA1),
+ index(!eucjp_jis0212_flag ? 'jis0208' : 'jis0212'));
+ }
+
+ // 3. Unset the euc-jp jis0212 flag.
+ eucjp_jis0212_flag = false;
+
+ // 4. If byte is not in the range 0xA1 to 0xFE, prepend byte
+ // to stream.
+ if (!inRange(bite, 0xA1, 0xFE))
+ stream.prepend(bite);
+
+ // 5. If code point is null, return error.
+ if (code_point === null)
+ return decoderError(fatal);
+
+ // 6. Return a code point whose value is code point.
+ return code_point;
+ }
+
+ // 6. If byte is in the range 0x00 to 0x7F, return a code point
+ // whose value is byte.
+ if (inRange(bite, 0x00, 0x7F))
+ return bite;
+
+ // 7. If byte is 0x8E, 0x8F, or in the range 0xA1 to 0xFE, set
+ // euc-jp lead to byte and return continue.
+ if (bite === 0x8E || bite === 0x8F || inRange(bite, 0xA1, 0xFE)) {
+ eucjp_lead = bite;
+ return null;
+ }
+
+ // 8. Return error.
+ return decoderError(fatal);
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ */
+ function EUCJPEncoder(options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007F))
+ return code_point;
+
+ // 3. If code point is U+00A5, return byte 0x5C.
+ if (code_point === 0x00A5)
+ return 0x5C;
+
+ // 4. If code point is U+203E, return byte 0x7E.
+ if (code_point === 0x203E)
+ return 0x7E;
+
+ // 5. If code point is in the range U+FF61 to U+FF9F, return two
+ // bytes whose values are 0x8E and code point − 0xFF61 + 0xA1.
+ if (inRange(code_point, 0xFF61, 0xFF9F))
+ return [0x8E, code_point - 0xFF61 + 0xA1];
+
+ // 6. If code point is U+2022, set it to U+FF0D.
+ if (code_point === 0x2022)
+ code_point = 0xFF0D;
+
+ // 7. Let pointer be the index pointer for code point in index
+ // jis0208.
+ var pointer = indexPointerFor(code_point, index('jis0208'));
+
+ // 8. If pointer is null, return error with code point.
+ if (pointer === null)
+ return encoderError(code_point);
+
+ // 9. Let lead be pointer / 94 + 0xA1.
+ var lead = div(pointer, 94) + 0xA1;
+
+ // 10. Let trail be pointer % 94 + 0xA1.
+ var trail = pointer % 94 + 0xA1;
+
+ // 11. Return two bytes whose values are lead and trail.
+ return [lead, trail];
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['euc-jp'] = function(options) {
+ return new EUCJPEncoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['euc-jp'] = function(options) {
+ return new EUCJPDecoder(options);
+ };
+
+ // 12.2 iso-2022-jp
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function ISO2022JPDecoder(options) {
+ var fatal = options.fatal;
+ /** @enum */
+ var states = {
+ ASCII: 0,
+ Roman: 1,
+ Katakana: 2,
+ LeadByte: 3,
+ TrailByte: 4,
+ EscapeStart: 5,
+ Escape: 6
+ };
+ // iso-2022-jp's decoder has an associated iso-2022-jp decoder
+ // state (initially ASCII), iso-2022-jp decoder output state
+ // (initially ASCII), iso-2022-jp lead (initially 0x00), and
+ // iso-2022-jp output flag (initially unset).
+ var /** @type {number} */ iso2022jp_decoder_state = states.ASCII,
+ /** @type {number} */ iso2022jp_decoder_output_state = states.ASCII,
+ /** @type {number} */ iso2022jp_lead = 0x00,
+ /** @type {boolean} */ iso2022jp_output_flag = false;
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // switching on iso-2022-jp decoder state:
+ switch (iso2022jp_decoder_state) {
+ default:
+ case states.ASCII:
+ // ASCII
+ // Based on byte:
+
+ // 0x1B
+ if (bite === 0x1B) {
+ // Set iso-2022-jp decoder state to escape start and return
+ // continue.
+ iso2022jp_decoder_state = states.EscapeStart;
+ return null;
+ }
+
+ // 0x00 to 0x7F, excluding 0x0E, 0x0F, and 0x1B
+ if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E
+ && bite !== 0x0F && bite !== 0x1B) {
+ // Unset the iso-2022-jp output flag and return a code point
+ // whose value is byte.
+ iso2022jp_output_flag = false;
+ return bite;
+ }
+
+ // end-of-stream
+ if (bite === end_of_stream) {
+ // Return finished.
+ return finished;
+ }
+
+ // Otherwise
+ // Unset the iso-2022-jp output flag and return error.
+ iso2022jp_output_flag = false;
+ return decoderError(fatal);
+
+ case states.Roman:
+ // Roman
+ // Based on byte:
+
+ // 0x1B
+ if (bite === 0x1B) {
+ // Set iso-2022-jp decoder state to escape start and return
+ // continue.
+ iso2022jp_decoder_state = states.EscapeStart;
+ return null;
+ }
+
+ // 0x5C
+ if (bite === 0x5C) {
+ // Unset the iso-2022-jp output flag and return code point
+ // U+00A5.
+ iso2022jp_output_flag = false;
+ return 0x00A5;
+ }
+
+ // 0x7E
+ if (bite === 0x7E) {
+ // Unset the iso-2022-jp output flag and return code point
+ // U+203E.
+ iso2022jp_output_flag = false;
+ return 0x203E;
+ }
+
+ // 0x00 to 0x7F, excluding 0x0E, 0x0F, 0x1B, 0x5C, and 0x7E
+ if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E && bite !== 0x0F
+ && bite !== 0x1B && bite !== 0x5C && bite !== 0x7E) {
+ // Unset the iso-2022-jp output flag and return a code point
+ // whose value is byte.
+ iso2022jp_output_flag = false;
+ return bite;
+ }
+
+ // end-of-stream
+ if (bite === end_of_stream) {
+ // Return finished.
+ return finished;
+ }
+
+ // Otherwise
+ // Unset the iso-2022-jp output flag and return error.
+ iso2022jp_output_flag = false;
+ return decoderError(fatal);
+
+ case states.Katakana:
+ // Katakana
+ // Based on byte:
+
+ // 0x1B
+ if (bite === 0x1B) {
+ // Set iso-2022-jp decoder state to escape start and return
+ // continue.
+ iso2022jp_decoder_state = states.EscapeStart;
+ return null;
+ }
+
+ // 0x21 to 0x5F
+ if (inRange(bite, 0x21, 0x5F)) {
+ // Unset the iso-2022-jp output flag and return a code point
+ // whose value is 0xFF61 + byte − 0x21.
+ iso2022jp_output_flag = false;
+ return 0xFF61 + bite - 0x21;
+ }
+
+ // end-of-stream
+ if (bite === end_of_stream) {
+ // Return finished.
+ return finished;
+ }
+
+ // Otherwise
+ // Unset the iso-2022-jp output flag and return error.
+ iso2022jp_output_flag = false;
+ return decoderError(fatal);
+
+ case states.LeadByte:
+ // Lead byte
+ // Based on byte:
+
+ // 0x1B
+ if (bite === 0x1B) {
+ // Set iso-2022-jp decoder state to escape start and return
+ // continue.
+ iso2022jp_decoder_state = states.EscapeStart;
+ return null;
+ }
+
+ // 0x21 to 0x7E
+ if (inRange(bite, 0x21, 0x7E)) {
+ // Unset the iso-2022-jp output flag, set iso-2022-jp lead
+ // to byte, iso-2022-jp decoder state to trail byte, and
+ // return continue.
+ iso2022jp_output_flag = false;
+ iso2022jp_lead = bite;
+ iso2022jp_decoder_state = states.TrailByte;
+ return null;
+ }
+
+ // end-of-stream
+ if (bite === end_of_stream) {
+ // Return finished.
+ return finished;
+ }
+
+ // Otherwise
+ // Unset the iso-2022-jp output flag and return error.
+ iso2022jp_output_flag = false;
+ return decoderError(fatal);
+
+ case states.TrailByte:
+ // Trail byte
+ // Based on byte:
+
+ // 0x1B
+ if (bite === 0x1B) {
+ // Set iso-2022-jp decoder state to escape start and return
+ // continue.
+ iso2022jp_decoder_state = states.EscapeStart;
+ return decoderError(fatal);
+ }
+
+ // 0x21 to 0x7E
+ if (inRange(bite, 0x21, 0x7E)) {
+ // 1. Set the iso-2022-jp decoder state to lead byte.
+ iso2022jp_decoder_state = states.LeadByte;
+
+ // 2. Let pointer be (iso-2022-jp lead − 0x21) × 94 + byte − 0x21.
+ var pointer = (iso2022jp_lead - 0x21) * 94 + bite - 0x21;
+
+ // 3. Let code point be the index code point for pointer in index jis0208.
+ var code_point = indexCodePointFor(pointer, index('jis0208'));
+
+ // 4. If code point is null, return error.
+ if (code_point === null)
+ return decoderError(fatal);
+
+ // 5. Return a code point whose value is code point.
+ return code_point;
+ }
+
+ // end-of-stream
+ if (bite === end_of_stream) {
+ // Set the iso-2022-jp decoder state to lead byte, prepend
+ // byte to stream, and return error.
+ iso2022jp_decoder_state = states.LeadByte;
+ stream.prepend(bite);
+ return decoderError(fatal);
+ }
+
+ // Otherwise
+ // Set iso-2022-jp decoder state to lead byte and return
+ // error.
+ iso2022jp_decoder_state = states.LeadByte;
+ return decoderError(fatal);
+
+ case states.EscapeStart:
+ // Escape start
+
+ // 1. If byte is either 0x24 or 0x28, set iso-2022-jp lead to
+ // byte, iso-2022-jp decoder state to escape, and return
+ // continue.
+ if (bite === 0x24 || bite === 0x28) {
+ iso2022jp_lead = bite;
+ iso2022jp_decoder_state = states.Escape;
+ return null;
+ }
+
+ // 2. Prepend byte to stream.
+ stream.prepend(bite);
+
+ // 3. Unset the iso-2022-jp output flag, set iso-2022-jp
+ // decoder state to iso-2022-jp decoder output state, and
+ // return error.
+ iso2022jp_output_flag = false;
+ iso2022jp_decoder_state = iso2022jp_decoder_output_state;
+ return decoderError(fatal);
+
+ case states.Escape:
+ // Escape
+
+ // 1. Let lead be iso-2022-jp lead and set iso-2022-jp lead to
+ // 0x00.
+ var lead = iso2022jp_lead;
+ iso2022jp_lead = 0x00;
+
+ // 2. Let state be null.
+ var state = null;
+
+ // 3. If lead is 0x28 and byte is 0x42, set state to ASCII.
+ if (lead === 0x28 && bite === 0x42)
+ state = states.ASCII;
+
+ // 4. If lead is 0x28 and byte is 0x4A, set state to Roman.
+ if (lead === 0x28 && bite === 0x4A)
+ state = states.Roman;
+
+ // 5. If lead is 0x28 and byte is 0x49, set state to Katakana.
+ if (lead === 0x28 && bite === 0x49)
+ state = states.Katakana;
+
+ // 6. If lead is 0x24 and byte is either 0x40 or 0x42, set
+ // state to lead byte.
+ if (lead === 0x24 && (bite === 0x40 || bite === 0x42))
+ state = states.LeadByte;
+
+ // 7. If state is non-null, run these substeps:
+ if (state !== null) {
+ // 1. Set iso-2022-jp decoder state and iso-2022-jp decoder
+ // output state to states.
+ iso2022jp_decoder_state = iso2022jp_decoder_state = state;
+
+ // 2. Let output flag be the iso-2022-jp output flag.
+ var output_flag = iso2022jp_output_flag;
+
+ // 3. Set the iso-2022-jp output flag.
+ iso2022jp_output_flag = true;
+
+ // 4. Return continue, if output flag is unset, and error
+ // otherwise.
+ return !output_flag ? null : decoderError(fatal);
+ }
+
+ // 8. Prepend lead and byte to stream.
+ stream.prepend([lead, bite]);
+
+ // 9. Unset the iso-2022-jp output flag, set iso-2022-jp
+ // decoder state to iso-2022-jp decoder output state and
+ // return error.
+ iso2022jp_output_flag = false;
+ iso2022jp_decoder_state = iso2022jp_decoder_output_state;
+ return decoderError(fatal);
+ }
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ */
+ function ISO2022JPEncoder(options) {
+ var fatal = options.fatal;
+ // iso-2022-jp's encoder has an associated iso-2022-jp encoder
+ // state which is one of ASCII, Roman, and jis0208 (initially
+ // ASCII).
+ /** @enum */
+ var states = {
+ ASCII: 0,
+ Roman: 1,
+ jis0208: 2
+ };
+ var /** @type {number} */ iso2022jp_state = states.ASCII;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream and iso-2022-jp encoder
+ // state is not ASCII, prepend code point to stream, set
+ // iso-2022-jp encoder state to ASCII, and return three bytes
+ // 0x1B 0x28 0x42.
+ if (code_point === end_of_stream &&
+ iso2022jp_state !== states.ASCII) {
+ stream.prepend(code_point);
+ return [0x1B, 0x28, 0x42];
+ }
+
+ // 2. If code point is end-of-stream and iso-2022-jp encoder
+ // state is ASCII, return finished.
+ if (code_point === end_of_stream && iso2022jp_state === states.ASCII)
+ return finished;
+
+ // 3. If iso-2022-jp encoder state is ASCII and code point is in
+ // the range U+0000 to U+007F, return a byte whose value is code
+ // point.
+ if (iso2022jp_state === states.ASCII &&
+ inRange(code_point, 0x0000, 0x007F))
+ return code_point;
+
+ // 4. If iso-2022-jp encoder state is Roman and code point is in
+ // the range U+0000 to U+007F, excluding U+005C and U+007E, or
+ // is U+00A5 or U+203E, run these substeps:
+ if (iso2022jp_state === states.Roman &&
+ inRange(code_point, 0x0000, 0x007F) &&
+ code_point !== 0x005C && code_point !== 0x007E) {
+
+ // 1. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007F))
+ return code_point;
+
+ // 2. If code point is U+00A5, return byte 0x5C.
+ if (code_point === 0x00A5)
+ return 0x5C;
+
+ // 3. If code point is U+203E, return byte 0x7E.
+ if (code_point === 0x203E)
+ return 0x7E;
+ }
+
+ // 5. If code point is in the range U+0000 to U+007F, and
+ // iso-2022-jp encoder state is not ASCII, prepend code point to
+ // stream, set iso-2022-jp encoder state to ASCII, and return
+ // three bytes 0x1B 0x28 0x42.
+ if (inRange(code_point, 0x0000, 0x007F) &&
+ iso2022jp_state !== states.ASCII) {
+ stream.prepend(code_point);
+ iso2022jp_state = states.ASCII;
+ return [0x1B, 0x28, 0x42];
+ }
+
+ // 6. If code point is either U+00A5 or U+203E, and iso-2022-jp
+ // encoder state is not Roman, prepend code point to stream, set
+ // iso-2022-jp encoder state to Roman, and return three bytes
+ // 0x1B 0x28 0x4A.
+ if ((code_point === 0x00A5 || code_point === 0x203E) &&
+ iso2022jp_state !== states.Roman) {
+ stream.prepend(code_point);
+ iso2022jp_state = states.Roman;
+ return [0x1B, 0x28, 0x4A];
+ }
+
+ // 7. If code point is U+2022, set it to U+FF0D.
+ if (code_point === 0x2022)
+ code_point = 0xFF0D;
+
+ // 8. Let pointer be the index pointer for code point in index
+ // jis0208.
+ var pointer = indexPointerFor(code_point, index('jis0208'));
+
+ // 9. If pointer is null, return error with code point.
+ if (pointer === null)
+ return encoderError(code_point);
+
+ // 10. If iso-2022-jp encoder state is not jis0208, prepend code
+ // point to stream, set iso-2022-jp encoder state to jis0208,
+ // and return three bytes 0x1B 0x24 0x42.
+ if (iso2022jp_state !== states.jis0208) {
+ stream.prepend(code_point);
+ iso2022jp_state = states.jis0208;
+ return [0x1B, 0x24, 0x42];
+ }
+
+ // 11. Let lead be pointer / 94 + 0x21.
+ var lead = div(pointer, 94) + 0x21;
+
+ // 12. Let trail be pointer % 94 + 0x21.
+ var trail = pointer % 94 + 0x21;
+
+ // 13. Return two bytes whose values are lead and trail.
+ return [lead, trail];
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['iso-2022-jp'] = function(options) {
+ return new ISO2022JPEncoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['iso-2022-jp'] = function(options) {
+ return new ISO2022JPDecoder(options);
+ };
+
+ // 12.3 shift_jis
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function ShiftJISDecoder(options) {
+ var fatal = options.fatal;
+ // shift_jis's decoder has an associated shift_jis lead (initially
+ // 0x00).
+ var /** @type {number} */ shiftjis_lead = 0x00;
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream and shift_jis lead is not 0x00,
+ // set shift_jis lead to 0x00 and return error.
+ if (bite === end_of_stream && shiftjis_lead !== 0x00) {
+ shiftjis_lead = 0x00;
+ return decoderError(fatal);
+ }
+
+ // 2. If byte is end-of-stream and shift_jis lead is 0x00,
+ // return finished.
+ if (bite === end_of_stream && shiftjis_lead === 0x00)
+ return finished;
+
+ // 3. If shift_jis lead is not 0x00, let lead be shift_jis lead,
+ // let pointer be null, set shift_jis lead to 0x00, and then run
+ // these substeps:
+ if (shiftjis_lead !== 0x00) {
+ var lead = shiftjis_lead;
+ var pointer = null;
+ shiftjis_lead = 0x00;
+
+ // 1. Let offset be 0x40, if byte is less than 0x7F, and 0x41
+ // otherwise.
+ var offset = (bite < 0x7F) ? 0x40 : 0x41;
+
+ // 2. Let lead offset be 0x81, if lead is less than 0xA0, and
+ // 0xC1 otherwise.
+ var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1;
+
+ // 3. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFC,
+ // set pointer to (lead − lead offset) × 188 + byte − offset.
+ if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC))
+ pointer = (lead - lead_offset) * 188 + bite - offset;
+
+ // 4. Let code point be null, if pointer is null, and the
+ // index code point for pointer in index jis0208 otherwise.
+ var code_point = (pointer === null) ? null :
+ indexCodePointFor(pointer, index('jis0208'));
+
+ // 5. If code point is null and pointer is in the range 8836
+ // to 10528, return a code point whose value is 0xE000 +
+ // pointer − 8836.
+ if (code_point === null && pointer !== null &&
+ inRange(pointer, 8836, 10528))
+ return 0xE000 + pointer - 8836;
+
+ // 6. If code point is null and byte is in the range 0x00 to
+ // 0x7F, prepend byte to stream.
+ if (code_point === null && inRange(bite, 0x00, 0x7F))
+ stream.prepend(bite);
+
+ // 7. If code point is null, return error.
+ if (code_point === null)
+ return decoderError(fatal);
+
+ // 8. Return a code point whose value is code point.
+ return code_point;
+ }
+
+ // 4. If byte is in the range 0x00 to 0x80, return a code point
+ // whose value is byte.
+ if (inRange(bite, 0x00, 0x80))
+ return bite;
+
+ // 5. If byte is in the range 0xA1 to 0xDF, return a code point
+ // whose value is 0xFF61 + byte − 0xA1.
+ if (inRange(bite, 0xA1, 0xDF))
+ return 0xFF61 + bite - 0xA1;
+
+ // 6. If byte is in the range 0x81 to 0x9F or 0xE0 to 0xFC, set
+ // shift_jis lead to byte and return continue.
+ if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) {
+ shiftjis_lead = bite;
+ return null;
+ }
+
+ // 7. Return error.
+ return decoderError(fatal);
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ */
+ function ShiftJISEncoder(options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+0080, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x0080))
+ return code_point;
+
+ // 3. If code point is U+00A5, return byte 0x5C.
+ if (code_point === 0x00A5)
+ return 0x5C;
+
+ // 4. If code point is U+203E, return byte 0x7E.
+ if (code_point === 0x203E)
+ return 0x7E;
+
+ // 5. If code point is in the range U+FF61 to U+FF9F, return a
+ // byte whose value is code point − 0xFF61 + 0xA1.
+ if (inRange(code_point, 0xFF61, 0xFF9F))
+ return code_point - 0xFF61 + 0xA1;
+
+ // 6. If code point is U+2022, set it to U+FF0D.
+ if (code_point === 0x2022)
+ code_point = 0xFF0D;
+
+ // 7. Let pointer be the index shift_jis pointer for code point.
+ var pointer = indexShiftJISPointerFor(code_point);
+
+ // 8. If pointer is null, return error with code point.
+ if (pointer === null)
+ return encoderError(code_point);
+
+ // 9. Let lead be pointer / 188.
+ var lead = div(pointer, 188);
+
+ // 10. Let lead offset be 0x81, if lead is less than 0x1F, and
+ // 0xC1 otherwise.
+ var lead_offset = (lead < 0x1F) ? 0x81 : 0xC1;
+
+ // 11. Let trail be pointer % 188.
+ var trail = pointer % 188;
+
+ // 12. Let offset be 0x40, if trail is less than 0x3F, and 0x41
+ // otherwise.
+ var offset = (trail < 0x3F) ? 0x40 : 0x41;
+
+ // 13. Return two bytes whose values are lead + lead offset and
+ // trail + offset.
+ return [lead + lead_offset, trail + offset];
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['shift_jis'] = function(options) {
+ return new ShiftJISEncoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['shift_jis'] = function(options) {
+ return new ShiftJISDecoder(options);
+ };
+
+ //
+ // 13. Legacy multi-byte Korean encodings
+ //
+
+ // 13.1 euc-kr
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function EUCKRDecoder(options) {
+ var fatal = options.fatal;
+
+ // euc-kr's decoder has an associated euc-kr lead (initially 0x00).
+ var /** @type {number} */ euckr_lead = 0x00;
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream and euc-kr lead is not 0x00, set
+ // euc-kr lead to 0x00 and return error.
+ if (bite === end_of_stream && euckr_lead !== 0) {
+ euckr_lead = 0x00;
+ return decoderError(fatal);
+ }
+
+ // 2. If byte is end-of-stream and euc-kr lead is 0x00, return
+ // finished.
+ if (bite === end_of_stream && euckr_lead === 0)
+ return finished;
+
+ // 3. If euc-kr lead is not 0x00, let lead be euc-kr lead, let
+ // pointer be null, set euc-kr lead to 0x00, and then run these
+ // substeps:
+ if (euckr_lead !== 0x00) {
+ var lead = euckr_lead;
+ var pointer = null;
+ euckr_lead = 0x00;
+
+ // 1. If byte is in the range 0x41 to 0xFE, set pointer to
+ // (lead − 0x81) × 190 + (byte − 0x41).
+ if (inRange(bite, 0x41, 0xFE))
+ pointer = (lead - 0x81) * 190 + (bite - 0x41);
+
+ // 2. Let code point be null, if pointer is null, and the
+ // index code point for pointer in index euc-kr otherwise.
+ var code_point = (pointer === null) ? null : indexCodePointFor(pointer, index('euc-kr'));
+
+ // 3. If code point is null and byte is in the range 0x00 to
+ // 0x7F, prepend byte to stream.
+ if (pointer === null && inRange(bite, 0x00, 0x7F))
+ stream.prepend(bite);
+
+ // 4. If code point is null, return error.
+ if (code_point === null)
+ return decoderError(fatal);
+
+ // 5. Return a code point whose value is code point.
+ return code_point;
+ }
+
+ // 4. If byte is in the range 0x00 to 0x7F, return a code point
+ // whose value is byte.
+ if (inRange(bite, 0x00, 0x7F))
+ return bite;
+
+ // 5. If byte is in the range 0x81 to 0xFE, set euc-kr lead to
+ // byte and return continue.
+ if (inRange(bite, 0x81, 0xFE)) {
+ euckr_lead = bite;
+ return null;
+ }
+
+ // 6. Return error.
+ return decoderError(fatal);
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ */
+ function EUCKREncoder(options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007F))
+ return code_point;
+
+ // 3. Let pointer be the index pointer for code point in index
+ // euc-kr.
+ var pointer = indexPointerFor(code_point, index('euc-kr'));
+
+ // 4. If pointer is null, return error with code point.
+ if (pointer === null)
+ return encoderError(code_point);
+
+ // 5. Let lead be pointer / 190 + 0x81.
+ var lead = div(pointer, 190) + 0x81;
+
+ // 6. Let trail be pointer % 190 + 0x41.
+ var trail = (pointer % 190) + 0x41;
+
+ // 7. Return two bytes whose values are lead and trail.
+ return [lead, trail];
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['euc-kr'] = function(options) {
+ return new EUCKREncoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['euc-kr'] = function(options) {
+ return new EUCKRDecoder(options);
+ };
+
+
+ //
+ // 14. Legacy miscellaneous encodings
+ //
+
+ // 14.1 replacement
+
+ // Not needed - API throws RangeError
+
+ // 14.2 utf-16
+
+ /**
+ * @param {number} code_unit
+ * @param {boolean} utf16be
+ * @return {!Array.<number>} bytes
+ */
+ function convertCodeUnitToBytes(code_unit, utf16be) {
+ // 1. Let byte1 be code unit >> 8.
+ var byte1 = code_unit >> 8;
+
+ // 2. Let byte2 be code unit & 0x00FF.
+ var byte2 = code_unit & 0x00FF;
+
+ // 3. Then return the bytes in order:
+ // utf-16be flag is set: byte1, then byte2.
+ if (utf16be)
+ return [byte1, byte2];
+ // utf-16be flag is unset: byte2, then byte1.
+ return [byte2, byte1];
+ }
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {boolean} utf16_be True if big-endian, false if little-endian.
+ * @param {{fatal: boolean}} options
+ */
+ function UTF16Decoder(utf16_be, options) {
+ var fatal = options.fatal;
+ var /** @type {?number} */ utf16_lead_byte = null,
+ /** @type {?number} */ utf16_lead_surrogate = null;
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream and either utf-16 lead byte or
+ // utf-16 lead surrogate is not null, set utf-16 lead byte and
+ // utf-16 lead surrogate to null, and return error.
+ if (bite === end_of_stream && (utf16_lead_byte !== null ||
+ utf16_lead_surrogate !== null)) {
+ return decoderError(fatal);
+ }
+
+ // 2. If byte is end-of-stream and utf-16 lead byte and utf-16
+ // lead surrogate are null, return finished.
+ if (bite === end_of_stream && utf16_lead_byte === null &&
+ utf16_lead_surrogate === null) {
+ return finished;
+ }
+
+ // 3. If utf-16 lead byte is null, set utf-16 lead byte to byte
+ // and return continue.
+ if (utf16_lead_byte === null) {
+ utf16_lead_byte = bite;
+ return null;
+ }
+
+ // 4. Let code unit be the result of:
+ var code_unit;
+ if (utf16_be) {
+ // utf-16be decoder flag is set
+ // (utf-16 lead byte << 8) + byte.
+ code_unit = (utf16_lead_byte << 8) + bite;
+ } else {
+ // utf-16be decoder flag is unset
+ // (byte << 8) + utf-16 lead byte.
+ code_unit = (bite << 8) + utf16_lead_byte;
+ }
+ // Then set utf-16 lead byte to null.
+ utf16_lead_byte = null;
+
+ // 5. If utf-16 lead surrogate is not null, let lead surrogate
+ // be utf-16 lead surrogate, set utf-16 lead surrogate to null,
+ // and then run these substeps:
+ if (utf16_lead_surrogate !== null) {
+ var lead_surrogate = utf16_lead_surrogate;
+ utf16_lead_surrogate = null;
+
+ // 1. If code unit is in the range U+DC00 to U+DFFF, return a
+ // code point whose value is 0x10000 + ((lead surrogate −
+ // 0xD800) << 10) + (code unit − 0xDC00).
+ if (inRange(code_unit, 0xDC00, 0xDFFF)) {
+ return 0x10000 + (lead_surrogate - 0xD800) * 0x400 +
+ (code_unit - 0xDC00);
+ }
+
+ // 2. Prepend the sequence resulting of converting code unit
+ // to bytes using utf-16be decoder flag to stream and return
+ // error.
+ stream.prepend(convertCodeUnitToBytes(code_unit, utf16_be));
+ return decoderError(fatal);
+ }
+
+ // 6. If code unit is in the range U+D800 to U+DBFF, set utf-16
+ // lead surrogate to code unit and return continue.
+ if (inRange(code_unit, 0xD800, 0xDBFF)) {
+ utf16_lead_surrogate = code_unit;
+ return null;
+ }
+
+ // 7. If code unit is in the range U+DC00 to U+DFFF, return
+ // error.
+ if (inRange(code_unit, 0xDC00, 0xDFFF))
+ return decoderError(fatal);
+
+ // 8. Return code point code unit.
+ return code_unit;
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {boolean} utf16_be True if big-endian, false if little-endian.
+ * @param {{fatal: boolean}} options
+ */
+ function UTF16Encoder(utf16_be, options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1. If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+FFFF, return the
+ // sequence resulting of converting code point to bytes using
+ // utf-16be encoder flag.
+ if (inRange(code_point, 0x0000, 0xFFFF))
+ return convertCodeUnitToBytes(code_point, utf16_be);
+
+ // 3. Let lead be ((code point − 0x10000) >> 10) + 0xD800,
+ // converted to bytes using utf-16be encoder flag.
+ var lead = convertCodeUnitToBytes(
+ ((code_point - 0x10000) >> 10) + 0xD800, utf16_be);
+
+ // 4. Let trail be ((code point − 0x10000) & 0x3FF) + 0xDC00,
+ // converted to bytes using utf-16be encoder flag.
+ var trail = convertCodeUnitToBytes(
+ ((code_point - 0x10000) & 0x3FF) + 0xDC00, utf16_be);
+
+ // 5. Return a byte sequence of lead followed by trail.
+ return lead.concat(trail);
+ };
+ }
+
+ // 14.3 utf-16be
+ /** @param {{fatal: boolean}} options */
+ encoders['utf-16be'] = function(options) {
+ return new UTF16Encoder(true, options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['utf-16be'] = function(options) {
+ return new UTF16Decoder(true, options);
+ };
+
+ // 14.4 utf-16le
+ /** @param {{fatal: boolean}} options */
+ encoders['utf-16le'] = function(options) {
+ return new UTF16Encoder(false, options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['utf-16le'] = function(options) {
+ return new UTF16Decoder(false, options);
+ };
+
+ // 14.5 x-user-defined
+
+ /**
+ * @constructor
+ * @implements {Decoder}
+ * @param {{fatal: boolean}} options
+ */
+ function XUserDefinedDecoder(options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream The stream of bytes being decoded.
+ * @param {number} bite The next byte read from the stream.
+ * @return {?(number|!Array.<number>)} The next code point(s)
+ * decoded, or null if not enough data exists in the input
+ * stream to decode a complete code point.
+ */
+ this.handler = function(stream, bite) {
+ // 1. If byte is end-of-stream, return finished.
+ if (bite === end_of_stream)
+ return finished;
+
+ // 2. If byte is in the range 0x00 to 0x7F, return a code point
+ // whose value is byte.
+ if (inRange(bite, 0x00, 0x7F))
+ return bite;
+
+ // 3. Return a code point whose value is 0xF780 + byte − 0x80.
+ return 0xF780 + bite - 0x80;
+ };
+ }
+
+ /**
+ * @constructor
+ * @implements {Encoder}
+ * @param {{fatal: boolean}} options
+ */
+ function XUserDefinedEncoder(options) {
+ var fatal = options.fatal;
+ /**
+ * @param {Stream} stream Input stream.
+ * @param {number} code_point Next code point read from the stream.
+ * @return {(number|!Array.<number>)} Byte(s) to emit.
+ */
+ this.handler = function(stream, code_point) {
+ // 1.If code point is end-of-stream, return finished.
+ if (code_point === end_of_stream)
+ return finished;
+
+ // 2. If code point is in the range U+0000 to U+007F, return a
+ // byte whose value is code point.
+ if (inRange(code_point, 0x0000, 0x007F))
+ return code_point;
+
+ // 3. If code point is in the range U+F780 to U+F7FF, return a
+ // byte whose value is code point − 0xF780 + 0x80.
+ if (inRange(code_point, 0xF780, 0xF7FF))
+ return code_point - 0xF780 + 0x80;
+
+ // 4. Return error with code point.
+ return encoderError(code_point);
+ };
+ }
+
+ /** @param {{fatal: boolean}} options */
+ encoders['x-user-defined'] = function(options) {
+ return new XUserDefinedEncoder(options);
+ };
+ /** @param {{fatal: boolean}} options */
+ decoders['x-user-defined'] = function(options) {
+ return new XUserDefinedDecoder(options);
+ };
+
+ if (!('TextEncoder' in global))
+ global['TextEncoder'] = TextEncoder;
+ if (!('TextDecoder' in global))
+ global['TextDecoder'] = TextDecoder;
+}(this));
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..add3a6f
--- /dev/null
+++ b/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "text-encoding",
+ "author": "Joshua Bell <inexorabletash at gmail.com>",
+ "contributors": [
+ "Rick Eyre <rick.eyre at outlook.com>"
+ ],
+ "version": "0.5.3",
+ "description": "Polyfill for the Encoding Living Standard's API.",
+ "main": "index.js",
+ "files": [
+ "index.js",
+ "lib/encoding.js",
+ "lib/encoding-indexes.js"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/inexorabletash/text-encoding.git"
+ },
+ "keywords": [
+ "encoding",
+ "decoding",
+ "living standard"
+ ],
+ "bugs": {
+ "url": "https://github.com/inexorabletash/text-encoding/issues"
+ },
+ "homepage": "https://github.com/inexorabletash/text-encoding"
+}
diff --git a/test/test-big5.js b/test/test-big5.js
new file mode 100644
index 0000000..8197c05
--- /dev/null
+++ b/test/test-big5.js
@@ -0,0 +1,22 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+test(
+ function () {
+ var bytes = [161,64,161,65,161,66,161,67,161,68,161,70,161,71,161,72,161,73,161,74,161,75,161,76,161,77,161,79,161,80,161,81,161,82,161,83,161,84,161,85,161,86,161,87,161,88,161,89,161,90,161,91,161,92,161,93,161,94,161,95,161,96,161,97,161,98,161,99,161,100,161,101,161,102,161,103,161,104,161,105,161,106,161,107,161,108,161,109,161,110,161,111,161,112,161,113,161,114,161,115,161,116,161,117,161,118,161,119,161,120,161,121,161,122,161,123,161,124,161,125,161,126,161,161,161,162,161,1 [...]
+ var string = "\u3000\uFF0C\u3001\u3002\uFF0E\uFF1B\uFF1A\uFF1F\uFF01\uFE30\u2026\u2025\uFE50\uFE52\u00B7\uFE54\uFE55\uFE56\uFE57\uFF5C\u2013\uFE31\u2014\uFE33\u2574\uFE34\uFE4F\uFF08\uFF09\uFE35\uFE36\uFF5B\uFF5D\uFE37\uFE38\u3014\u3015\uFE39\uFE3A\u3010\u3011\uFE3B\uFE3C\u300A\u300B\uFE3D\uFE3E\u3008\u3009\uFE3F\uFE40\u300C\u300D\uFE41\uFE42\u300E\u300F\uFE43\uFE44\uFE59\uFE5A\uFE5B\uFE5C\uFE5D\uFE5E\u2018\u2019\u201C\u201D\u301D\u301E\u2035\u2032\uFF03\uFF06\uFF0A\u203B\u00A7\u3003 [...]
+ assert_equals(new TextDecoder("big5").decode(new Uint8Array(bytes)), string, "decoded");
+ },
+ "big5"
+);
diff --git a/test/test-euc-jp.js b/test/test-euc-jp.js
new file mode 100644
index 0000000..3822a28
--- /dev/null
+++ b/test/test-euc-jp.js
@@ -0,0 +1,22 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+test(
+ function () {
+ var bytes = [161,161,161,162,161,163,161,164,161,165,161,166,161,167,161,168,161,169,161,170,161,171,161,172,161,173,161,174,161,175,161,176,161,177,161,178,161,179,161,180,161,181,161,182,161,183,161,184,161,185,161,186,161,187,161,188,161,189,161,190,161,191,161,192,161,195,161,196,161,197,161,198,161,199,161,200,161,201,161,202,161,203,161,204,161,205,161,206,161,207,161,208,161,209,161,210,161,211,161,212,161,213,161,214,161,215,161,216,161,217,161,218,161,219,161,220,161,222,161 [...]
+ var string = "\u3000\u3001\u3002\uFF0C\uFF0E\u30FB\uFF1A\uFF1B\uFF1F\uFF01\u309B\u309C\u00B4\uFF40\u00A8\uFF3E\uFFE3\uFF3F\u30FD\u30FE\u309D\u309E\u3003\u4EDD\u3005\u3006\u3007\u30FC\u2015\u2010\uFF0F\uFF3C\uFF5C\u2026\u2025\u2018\u2019\u201C\u201D\uFF08\uFF09\u3014\u3015\uFF3B\uFF3D\uFF5B\uFF5D\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011\uFF0B\u00B1\u00D7\u00F7\uFF1D\u2260\uFF1C\uFF1E\u2266\u2267\u221E\u2234\u2642\u2640\u00B0\u2032\u2033\u2103\uFFE5\uFF04\uFF05\uFF03 [...]
+ assert_equals(new TextDecoder("euc-jp").decode(new Uint8Array(bytes)), string, "decoded");
+ },
+ "euc-jp"
+);
diff --git a/test/test-euc-kr.js b/test/test-euc-kr.js
new file mode 100644
index 0000000..7751946
--- /dev/null
+++ b/test/test-euc-kr.js
@@ -0,0 +1,22 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+test(
+ function () {
+ var bytes = [161,161,161,162,161,163,161,164,161,165,161,166,161,167,161,168,161,169,161,170,161,171,161,172,161,173,161,174,161,175,161,176,161,177,161,178,161,179,161,180,161,181,161,182,161,183,161,184,161,185,161,186,161,187,161,188,161,189,161,190,161,191,161,192,161,193,161,194,161,195,161,196,161,197,161,198,161,199,161,200,161,201,161,202,161,203,161,204,161,205,161,206,161,207,161,208,161,209,161,210,161,211,161,212,161,213,161,214,161,215,161,216,161,217,161,218,161,219,161 [...]
+ var string = "\u3000\u3001\u3002\u00B7\u2025\u2026\u00A8\u3003\u00AD\u2015\u2225\uFF3C\u223C\u2018\u2019\u201C\u201D\u3014\u3015\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011\u00B1\u00D7\u00F7\u2260\u2264\u2265\u221E\u2234\u00B0\u2032\u2033\u2103\u212B\uFFE0\uFFE1\uFFE5\u2642\u2640\u2220\u22A5\u2312\u2202\u2207\u2261\u2252\u00A7\u203B\u2606\u2605\u25CB\u25CF\u25CE\u25C7\u25C6\u25A1\u25A0\u25B3\u25B2\u25BD\u25BC\u2192\u2190\u2191\u2193\u2194\u3013\u226A\u226B\u221A\u223D [...]
+ assert_equals(new TextDecoder("euc-kr").decode(new Uint8Array(bytes)), string, "decoded");
+ },
+ "euc-kr"
+);
diff --git a/test/test-gbk.js b/test/test-gbk.js
new file mode 100644
index 0000000..0b09214
--- /dev/null
+++ b/test/test-gbk.js
@@ -0,0 +1,22 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+test(
+ function () {
+ var bytes = [129,64,129,65,129,66,129,67,129,68,129,69,129,70,129,71,129,72,129,73,129,74,129,75,129,76,129,77,129,78,129,79,129,80,129,81,129,82,129,83,129,84,129,85,129,86,129,87,129,88,129,89,129,90,129,91,129,92,129,93,129,94,129,95,129,96,129,97,129,98,129,99,129,100,129,101,129,102,129,103,129,104,129,105,129,106,129,107,129,108,129,109,129,110,129,111,129,112,129,113,129,114,129,115,129,116,129,117,129,118,129,119,129,120,129,121,129,122,129,123,129,124,129,125,129,126,129,128 [...]
+ var string = "\u4E02\u4E04\u4E05\u4E06\u4E0F\u4E12\u4E17\u4E1F\u4E20\u4E21\u4E23\u4E26\u4E29\u4E2E\u4E2F\u4E31\u4E33\u4E35\u4E37\u4E3C\u4E40\u4E41\u4E42\u4E44\u4E46\u4E4A\u4E51\u4E55\u4E57\u4E5A\u4E5B\u4E62\u4E63\u4E64\u4E65\u4E67\u4E68\u4E6A\u4E6B\u4E6C\u4E6D\u4E6E\u4E6F\u4E72\u4E74\u4E75\u4E76\u4E77\u4E78\u4E79\u4E7A\u4E7B\u4E7C\u4E7D\u4E7F\u4E80\u4E81\u4E82\u4E83\u4E84\u4E85\u4E87\u4E8A\u4E90\u4E96\u4E97\u4E99\u4E9C\u4E9D\u4E9E\u4EA3\u4EAA\u4EAF\u4EB0\u4EB1\u4EB4\u4EB6\u4EB7\u4EB8 [...]
+ assert_equals(new TextDecoder("gbk").decode(new Uint8Array(bytes)), string, "decoded");
+ },
+ "gbk"
+);
diff --git a/test/test-iso-2022-jp.js b/test/test-iso-2022-jp.js
new file mode 100644
index 0000000..a84221c
--- /dev/null
+++ b/test/test-iso-2022-jp.js
@@ -0,0 +1,22 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+test(
+ function () {
+ var bytes = [27,36,66,33,33,33,34,33,35,33,36,33,37,33,38,33,39,33,40,33,41,33,42,33,43,33,44,33,45,33,46,33,47,33,48,33,49,33,50,33,51,33,52,33,53,33,54,33,55,33,56,33,57,33,58,33,59,33,60,33,61,33,62,33,63,33,64,33,67,33,68,33,69,33,70,33,71,33,72,33,73,33,74,33,75,33,76,33,77,33,78,33,79,33,80,33,81,33,82,33,83,33,84,33,85,33,86,33,87,33,88,33,89,33,90,33,91,33,92,33,94,33,95,33,96,33,97,33,98,33,99,33,100,33,101,33,102,33,103,33,104,33,105,33,106,33,107,33,108,33,109,33,110,33,11 [...]
+ var string = "\u3000\u3001\u3002\uFF0C\uFF0E\u30FB\uFF1A\uFF1B\uFF1F\uFF01\u309B\u309C\u00B4\uFF40\u00A8\uFF3E\uFFE3\uFF3F\u30FD\u30FE\u309D\u309E\u3003\u4EDD\u3005\u3006\u3007\u30FC\u2015\u2010\uFF0F\uFF3C\uFF5C\u2026\u2025\u2018\u2019\u201C\u201D\uFF08\uFF09\u3014\u3015\uFF3B\uFF3D\uFF5B\uFF5D\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011\uFF0B\u00B1\u00D7\u00F7\uFF1D\u2260\uFF1C\uFF1E\u2266\u2267\u221E\u2234\u2642\u2640\u00B0\u2032\u2033\u2103\uFFE5\uFF04\uFF05\uFF03 [...]
+ assert_equals(new TextDecoder("iso-2022-jp").decode(new Uint8Array(bytes)), string, "decoded");
+ },
+ "iso-2022-jp"
+);
diff --git a/test/test-misc.js b/test/test-misc.js
new file mode 100644
index 0000000..d161be2
--- /dev/null
+++ b/test/test-misc.js
@@ -0,0 +1,321 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+// NOTE: Requires testharness.js
+// http://www.w3.org/2008/webapps/wiki/Harness
+
+var UTF_ENCODINGS = ['utf-8', 'utf-16le', 'utf-16be'];
+
+var LEGACY_ENCODINGS = [
+ 'ibm866', 'iso-8859-2', 'iso-8859-3', 'iso-8859-4', 'iso-8859-5',
+ 'iso-8859-6', 'iso-8859-7', 'iso-8859-8', 'iso-8859-10',
+ 'iso-8859-13', 'iso-8859-14', 'iso-8859-15', 'iso-8859-16', 'koi8-r',
+ 'koi8-u', 'macintosh', 'windows-874', 'windows-1250', 'windows-1251',
+ 'windows-1252', 'windows-1253', 'windows-1254', 'windows-1255',
+ 'windows-1256', 'windows-1257', 'windows-1258', 'x-mac-cyrillic',
+ 'gbk', 'gb18030', 'big5', 'euc-jp', 'iso-2022-jp', 'shift_jis',
+ 'euc-kr'
+];
+
+// Miscellaneous tests
+
+test(function() {
+ assert_false(/\[native code\]/.test(String(TextDecoder)),
+ 'Native implementation present - polyfill not tested.');
+}, 'TextDecoder Polyfill (will fail if natively supported)');
+
+test(function() {
+ assert_false(/\[native code\]/.test(String(TextEncoder)),
+ 'Native implementation present - polyfill not tested.');
+}, 'TextEncoder Polyfill (will fail if natively supported)');
+
+test(function() {
+ assert_true('encoding' in new TextEncoder());
+ assert_equals(new TextEncoder().encoding, 'utf-8');
+ assert_equals(new TextEncoder('utf-16le').encoding, 'utf-16le');
+
+ assert_true('encoding' in new TextDecoder());
+ assert_equals(new TextDecoder().encoding, 'utf-8');
+ assert_equals(new TextDecoder('utf-16le').encoding, 'utf-16le');
+ assert_true('fatal' in new TextDecoder());
+ assert_false(new TextDecoder('utf-8').fatal);
+ assert_true(new TextDecoder('utf-8', {fatal: true}).fatal);
+ assert_true('ignoreBOM' in new TextDecoder());
+ assert_false(new TextDecoder('utf-8').ignoreBOM);
+ assert_true(new TextDecoder('utf-8', {ignoreBOM: true}).ignoreBOM);
+}, 'Attributes');
+
+test(function() {
+ var badStrings = [
+ { input: '\ud800', expected: '\ufffd' }, // Surrogate half
+ { input: '\udc00', expected: '\ufffd' }, // Surrogate half
+ { input: 'abc\ud800def', expected: 'abc\ufffddef' }, // Surrogate half
+ { input: 'abc\udc00def', expected: 'abc\ufffddef' }, // Surrogate half
+ { input: '\udc00\ud800', expected: '\ufffd\ufffd' } // Wrong order
+ ];
+
+ badStrings.forEach(
+ function(t) {
+ var encoded = new TextEncoder('utf-8').encode(t.input);
+ var decoded = new TextDecoder('utf-8').decode(encoded);
+ assert_equals(t.expected, decoded);
+ });
+}, 'bad data');
+
+test(function() {
+ var bad = [
+ { encoding: 'utf-8', input: [0xC0] }, // ends early
+ { encoding: 'utf-8', input: [0xC0, 0x00] }, // invalid trail
+ { encoding: 'utf-8', input: [0xC0, 0xC0] }, // invalid trail
+ { encoding: 'utf-8', input: [0xE0] }, // ends early
+ { encoding: 'utf-8', input: [0xE0, 0x00] }, // invalid trail
+ { encoding: 'utf-8', input: [0xE0, 0xC0] }, // invalid trail
+ { encoding: 'utf-8', input: [0xE0, 0x80, 0x00] }, // invalid trail
+ { encoding: 'utf-8', input: [0xE0, 0x80, 0xC0] }, // invalid trail
+ { encoding: 'utf-8', input: [0xFC, 0x80, 0x80, 0x80, 0x80, 0x80] }, // > 0x10FFFF
+ { encoding: 'utf-16le', input: [0x00] }, // truncated code unit
+ { encoding: 'utf-16le', input: [0x00, 0xd8] }, // surrogate half
+ { encoding: 'utf-16le', input: [0x00, 0xd8, 0x00, 0x00] }, // surrogate half
+ { encoding: 'utf-16le', input: [0x00, 0xdc, 0x00, 0x00] }, // trail surrogate
+ { encoding: 'utf-16le', input: [0x00, 0xdc, 0x00, 0xd8] } // swapped surrogates
+ // TODO: Single byte encoding cases
+ ];
+
+ bad.forEach(
+ function(t) {
+ assert_throws({name: 'TypeError'}, function() {
+ new TextDecoder(t.encoding, {fatal: true}).decode(new Uint8Array(t.input));
+ });
+ });
+}, 'fatal flag');
+
+test(function() {
+ var encodings = [
+ { label: 'utf-8', encoding: 'utf-8' },
+ { label: 'utf-16', encoding: 'utf-16le' },
+ { label: 'utf-16le', encoding: 'utf-16le' },
+ { label: 'utf-16be', encoding: 'utf-16be' },
+ { label: 'ascii', encoding: 'windows-1252' },
+ { label: 'iso-8859-1', encoding: 'windows-1252' }
+ ];
+
+ encodings.forEach(
+ function(test) {
+ assert_equals(new TextDecoder(test.label.toLowerCase()).encoding, test.encoding);
+ assert_equals(new TextDecoder(test.label.toUpperCase()).encoding, test.encoding);
+ });
+}, 'Encoding names are case insensitive');
+
+test(function() {
+ var utf8_bom = [0xEF, 0xBB, 0xBF];
+ var utf8 = [0x7A, 0xC2, 0xA2, 0xE6, 0xB0, 0xB4, 0xF0, 0x9D, 0x84, 0x9E, 0xF4, 0x8F, 0xBF, 0xBD];
+
+ var utf16le_bom = [0xff, 0xfe];
+ var utf16le = [0x7A, 0x00, 0xA2, 0x00, 0x34, 0x6C, 0x34, 0xD8, 0x1E, 0xDD, 0xFF, 0xDB, 0xFD, 0xDF];
+
+ var utf16be_bom = [0xfe, 0xff];
+ var utf16be = [0x00, 0x7A, 0x00, 0xA2, 0x6C, 0x34, 0xD8, 0x34, 0xDD, 0x1E, 0xDB, 0xFF, 0xDF, 0xFD];
+
+ var string = 'z\xA2\u6C34\uD834\uDD1E\uDBFF\uDFFD'; // z, cent, CJK water, G-Clef, Private-use character
+
+ // missing BOMs
+ assert_equals(new TextDecoder('utf-8').decode(new Uint8Array(utf8)), string);
+ assert_equals(new TextDecoder('utf-16le').decode(new Uint8Array(utf16le)), string);
+ assert_equals(new TextDecoder('utf-16be').decode(new Uint8Array(utf16be)), string);
+
+ // matching BOMs
+ assert_equals(new TextDecoder('utf-8').decode(new Uint8Array(utf8_bom.concat(utf8))), string);
+ assert_equals(new TextDecoder('utf-16le').decode(new Uint8Array(utf16le_bom.concat(utf16le))), string);
+ assert_equals(new TextDecoder('utf-16be').decode(new Uint8Array(utf16be_bom.concat(utf16be))), string);
+
+ // matching BOMs split
+ var decoder8 = new TextDecoder('utf-8');
+ assert_equals(decoder8.decode(new Uint8Array(utf8_bom.slice(0, 1)), {stream: true}), '');
+ assert_equals(decoder8.decode(new Uint8Array(utf8_bom.slice(1).concat(utf8))), string);
+ assert_equals(decoder8.decode(new Uint8Array(utf8_bom.slice(0, 2)), {stream: true}), '');
+ assert_equals(decoder8.decode(new Uint8Array(utf8_bom.slice(2).concat(utf8))), string);
+ var decoder16le = new TextDecoder('utf-16le');
+ assert_equals(decoder16le.decode(new Uint8Array(utf16le_bom.slice(0, 1)), {stream: true}), '');
+ assert_equals(decoder16le.decode(new Uint8Array(utf16le_bom.slice(1).concat(utf16le))), string);
+ var decoder16be = new TextDecoder('utf-16be');
+ assert_equals(decoder16be.decode(new Uint8Array(utf16be_bom.slice(0, 1)), {stream: true}), '');
+ assert_equals(decoder16be.decode(new Uint8Array(utf16be_bom.slice(1).concat(utf16be))), string);
+
+ // mismatching BOMs
+ assert_not_equals(new TextDecoder('utf-8').decode(new Uint8Array(utf16le_bom.concat(utf8))), string);
+ assert_not_equals(new TextDecoder('utf-8').decode(new Uint8Array(utf16be_bom.concat(utf8))), string);
+ assert_not_equals(new TextDecoder('utf-16le').decode(new Uint8Array(utf8_bom.concat(utf16le))), string);
+ assert_not_equals(new TextDecoder('utf-16le').decode(new Uint8Array(utf16be_bom.concat(utf16le))), string);
+ assert_not_equals(new TextDecoder('utf-16be').decode(new Uint8Array(utf8_bom.concat(utf16be))), string);
+ assert_not_equals(new TextDecoder('utf-16be').decode(new Uint8Array(utf16le_bom.concat(utf16be))), string);
+
+ // ignore BOMs
+ assert_equals(new TextDecoder('utf-8', {ignoreBOM: true})
+ .decode(new Uint8Array(utf8_bom.concat(utf8))),
+ '\uFEFF' + string);
+ assert_equals(new TextDecoder('utf-16le', {ignoreBOM: true})
+ .decode(new Uint8Array(utf16le_bom.concat(utf16le))),
+ '\uFEFF' + string);
+ assert_equals(new TextDecoder('utf-16be', {ignoreBOM: true})
+ .decode(new Uint8Array(utf16be_bom.concat(utf16be))),
+ '\uFEFF' + string);
+}, 'Byte-order marks');
+
+test(function() {
+ assert_equals(new TextDecoder('utf-8').encoding, 'utf-8'); // canonical case
+ assert_equals(new TextDecoder('UTF-16').encoding, 'utf-16le'); // canonical case and name
+ assert_equals(new TextDecoder('UTF-16BE').encoding, 'utf-16be'); // canonical case and name
+ assert_equals(new TextDecoder('iso8859-1').encoding, 'windows-1252'); // canonical case and name
+ assert_equals(new TextDecoder('iso-8859-1').encoding, 'windows-1252'); // canonical case and name
+}, 'Encoding names');
+
+test(function() {
+ ['utf-8', 'utf-16le', 'utf-16be'].forEach(function(encoding) {
+ var string = '\x00123ABCabc\x80\xFF\u0100\u1000\uFFFD\uD800\uDC00\uDBFF\uDFFF';
+ var encoded = new TextEncoder(encoding).encode(string);
+
+ for (var len = 1; len <= 5; ++len) {
+ var out = '', decoder = new TextDecoder(encoding);
+ for (var i = 0; i < encoded.length; i += len) {
+ var sub = [];
+ for (var j = i; j < encoded.length && j < i + len; ++j) {
+ sub.push(encoded[j]);
+ }
+ out += decoder.decode(new Uint8Array(sub), {stream: true});
+ }
+ out += decoder.decode();
+ assert_equals(out, string, 'streaming decode ' + encoding);
+ }
+ });
+}, 'Streaming Decode');
+
+test(function() {
+ var jis = [0x82, 0xC9, 0x82, 0xD9, 0x82, 0xF1];
+ var expected = '\u306B\u307B\u3093'; // Nihon
+ assert_equals(new TextDecoder('shift_jis').decode(new Uint8Array(jis)), expected);
+}, 'Shift_JIS Decode');
+
+test(function() {
+ var encodings = ['utf-8'].concat(LEGACY_ENCODINGS);
+
+ encodings.forEach(function(encoding) {
+ var string = '', bytes = [];
+ for (var i = 0; i < 128; ++i) {
+
+ // Encodings that have escape codes in 0x00-0x7F
+ if (encoding === 'iso-2022-jp' &&
+ (i === 0x0E || i === 0x0F || i === 0x1B))
+ continue;
+
+ string += String.fromCharCode(i);
+ bytes.push(i);
+ }
+ var ascii_encoded = new TextEncoder('utf-8').encode(string);
+ assert_equals(new TextDecoder(encoding).decode(ascii_encoded), string, encoding);
+ //assert_array_equals(new TextEncoder(encoding).encode(string), bytes, encoding);
+ });
+}, 'Supersets of ASCII decode ASCII correctly');
+
+test(function() {
+ assert_throws({name: 'TypeError'}, function() { new TextDecoder('utf-8', {fatal: true}).decode(new Uint8Array([0xff])); });
+ // This should not hang:
+ new TextDecoder('utf-8').decode(new Uint8Array([0xff]));
+
+ assert_throws({name: 'TypeError'}, function() { new TextDecoder('utf-16le', {fatal: true}).decode(new Uint8Array([0x00])); });
+ // This should not hang:
+ new TextDecoder('utf-16le').decode(new Uint8Array([0x00]));
+
+ assert_throws({name: 'TypeError'}, function() { new TextDecoder('utf-16be', {fatal: true}).decode(new Uint8Array([0x00])); });
+ // This should not hang:
+ new TextDecoder('utf-16be').decode(new Uint8Array([0x00]));
+}, 'Non-fatal errors at EOF');
+
+test(function() {
+ UTF_ENCODINGS.forEach(function(encoding) {
+ assert_equals(new TextDecoder(encoding).encoding, encoding);
+ assert_equals(new TextEncoder(encoding).encoding, encoding);
+ });
+
+ LEGACY_ENCODINGS.forEach(function(encoding) {
+ assert_equals(new TextDecoder(encoding).encoding, encoding);
+ assert_throws({name: 'RangeError'}, function() { new TextEncoder(encoding); });
+ });
+}, 'Non-UTF encodings supported only for decode, not encode');
+
+test(function() {
+ [
+ 'csiso2022kr',
+ 'hz-gb-2312',
+ 'iso-2022-cn',
+ 'iso-2022-cn-ext',
+ 'iso-2022-kr'
+ ].forEach(function(encoding) {
+
+ assert_throws({name: 'RangeError'},
+ function() { new TextEncoder(encoding); });
+
+ assert_throws({name: 'RangeError'},
+ function() {
+ var decoder = new TextDecoder(encoding, {fatal: true});
+ });
+
+ assert_throws({name: 'RangeError'},
+ function() {
+ var decoder = new TextDecoder(encoding, {fatal: false});
+ });
+ });
+}, 'Replacement encoding labels');
+
+test(function() {
+ var decoder = new TextDecoder();
+ var bytes = [65, 66, 97, 98, 99, 100, 101, 102, 103, 104, 67, 68, 69, 70, 71, 72];
+ var chars = 'ABabcdefghCDEFGH';
+ var buffer = new Uint8Array(bytes).buffer;
+ assert_equals(decoder.decode(buffer), chars,
+ 'Decoding from ArrayBuffer should match expected text.');
+
+ ['Uint8Array', 'Int8Array', 'Uint8ClampedArray',
+ 'Uint16Array', 'Int16Array',
+ 'Uint32Array', 'Int32Array',
+ 'Float32Array', 'Float64Array'].forEach(function(typeName) {
+ var type = self[typeName];
+
+ var array = new type(buffer);
+ assert_equals(decoder.decode(array), chars,
+ 'Decoding from ' + typeName + ' should match expected text.');
+
+ var subset = new type(buffer, type.BYTES_PER_ELEMENT, 8 / type.BYTES_PER_ELEMENT);
+ assert_equals(decoder.decode(subset),
+ chars.substring(type.BYTES_PER_ELEMENT, type.BYTES_PER_ELEMENT + 8),
+ 'Decoding from ' + typeName + ' should match expected text.');
+ });
+}, 'ArrayBuffer, ArrayBufferView and buffer offsets');
+
+test(function() {
+ assert_throws({name: 'RangeError'},
+ function() { new TextDecoder(null); },
+ 'Null should coerce to "null" and be invalid encoding name.');
+
+ assert_throws({name: 'TypeError'},
+ function() { new TextDecoder('utf-8', ''); },
+ 'String should not coerce to dictionary.');
+
+ assert_throws({name: 'TypeError'},
+ function() { new TextDecoder('utf-8').decode(null, ''); },
+ 'String should not coerce to dictionary.');
+
+ assert_throws({name: 'RangeError'},
+ function() { new TextEncoder(null); },
+ 'Null should coerce to "null" and be invalid encoding name.');
+}, 'Invalid parameters');
diff --git a/test/test-shift_jis.js b/test/test-shift_jis.js
new file mode 100644
index 0000000..a01e49e
--- /dev/null
+++ b/test/test-shift_jis.js
@@ -0,0 +1,22 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+test(
+ function () {
+ var bytes = [129,64,129,65,129,66,129,67,129,68,129,69,129,70,129,71,129,72,129,73,129,74,129,75,129,76,129,77,129,78,129,79,129,80,129,81,129,82,129,83,129,84,129,85,129,86,129,87,129,88,129,89,129,90,129,91,129,92,129,93,129,94,129,95,129,98,129,99,129,100,129,101,129,102,129,103,129,104,129,105,129,106,129,107,129,108,129,109,129,110,129,111,129,112,129,113,129,114,129,115,129,116,129,117,129,118,129,119,129,120,129,121,129,122,129,123,129,125,129,126,129,128,129,129,129,130,129,1 [...]
+ var string = "\u3000\u3001\u3002\uFF0C\uFF0E\u30FB\uFF1A\uFF1B\uFF1F\uFF01\u309B\u309C\u00B4\uFF40\u00A8\uFF3E\uFFE3\uFF3F\u30FD\u30FE\u309D\u309E\u3003\u4EDD\u3005\u3006\u3007\u30FC\u2015\u2010\uFF0F\uFF3C\uFF5C\u2026\u2025\u2018\u2019\u201C\u201D\uFF08\uFF09\u3014\u3015\uFF3B\uFF3D\uFF5B\uFF5D\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011\uFF0B\u00B1\u00D7\u00F7\uFF1D\u2260\uFF1C\uFF1E\u2266\u2267\u221E\u2234\u2642\u2640\u00B0\u2032\u2033\u2103\uFFE5\uFF04\uFF05\uFF03 [...]
+ assert_equals(new TextDecoder("shift_jis").decode(new Uint8Array(bytes)), string, "decoded");
+ },
+ "shift_jis"
+);
diff --git a/test/test-utf.js b/test/test-utf.js
new file mode 100644
index 0000000..57b5c3f
--- /dev/null
+++ b/test/test-utf.js
@@ -0,0 +1,163 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+// NOTE: Requires testharness.js
+// http://www.w3.org/2008/webapps/wiki/Harness
+
+// Extension to testharness.js API which avoids logging enormous strings
+// on a coding failure.
+function assert_string_equals(actual, expected, description) {
+ // short circuit success case
+ if (actual === expected) {
+ assert_true(true, description + ": <actual> === <expected>");
+ return;
+ }
+
+ // length check
+ assert_equals(actual.length, expected.length,
+ description + ": string lengths");
+
+ for (var i = 0; i < actual.length; i++) {
+ var a = actual.charCodeAt(i);
+ var b = expected.charCodeAt(i);
+ if (a !== b)
+ assert_true(false,
+ description +
+ ": code unit " + i.toString() + " unequal: " +
+ cpname(a) + " != " + cpname(b)); // doesn't return
+ }
+
+ // It should be impossible to get here, because the initial
+ // comparison failed, so either the length comparison or the
+ // codeunit-by-codeunit comparison should also fail.
+ assert_true(false, description + ": failed to detect string difference");
+}
+
+// Inspired by:
+// http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
+function encode_utf8(string) {
+ var utf8 = unescape(encodeURIComponent(string));
+ var octets = new Uint8Array(utf8.length), i;
+ for (i = 0; i < utf8.length; i += 1) {
+ octets[i] = utf8.charCodeAt(i);
+ }
+ return octets;
+}
+
+function decode_utf8(octets) {
+ var utf8 = String.fromCharCode.apply(null, octets);
+ return decodeURIComponent(escape(utf8));
+}
+
+// Helpers for test_utf_roundtrip.
+function cpname(n) {
+ if (n+0 !== n)
+ return n.toString();
+ var w = (n <= 0xFFFF) ? 4 : 6;
+ return 'U+' + ('000000' + n.toString(16).toUpperCase()).slice(-w);
+}
+
+function genblock(from, len, skip) {
+ var block = [];
+ for (var i = 0; i < len; i += skip) {
+ var cp = from + i;
+ if (0xD800 <= cp && cp <= 0xDFFF)
+ continue;
+ if (cp < 0x10000) {
+ block.push(String.fromCharCode(cp));
+ continue;
+ }
+ cp = cp - 0x10000;
+ block.push(String.fromCharCode(0xD800 + (cp >> 10)));
+ block.push(String.fromCharCode(0xDC00 + (cp & 0x3FF)));
+ }
+ return block.join('');
+}
+
+function test_utf_roundtrip () {
+ var MIN_CODEPOINT = 0;
+ var MAX_CODEPOINT = 0x10FFFF;
+ var BLOCK_SIZE = 0x1000;
+ var SKIP_SIZE = 31;
+
+ var TE_U16LE = new TextEncoder("UTF-16LE");
+ var TD_U16LE = new TextDecoder("UTF-16LE");
+
+ var TE_U16BE = new TextEncoder("UTF-16BE");
+ var TD_U16BE = new TextDecoder("UTF-16BE");
+
+ var TE_U8 = new TextEncoder("UTF-8");
+ var TD_U8 = new TextDecoder("UTF-8");
+
+ for (var i = MIN_CODEPOINT; i < MAX_CODEPOINT; i += BLOCK_SIZE) {
+ var block_tag = cpname(i) + " - " + cpname(i + BLOCK_SIZE - 1);
+ var block = genblock(i, BLOCK_SIZE, SKIP_SIZE);
+
+ // test UTF-16LE, UTF-16BE, and UTF-8 encodings against themselves
+ var encoded = TE_U16LE.encode(block);
+ var decoded = TD_U16LE.decode(encoded);
+ assert_string_equals(block, decoded, "UTF-16LE round trip " + block_tag);
+
+ encoded = TE_U16BE.encode(block);
+ decoded = TD_U16BE.decode(encoded);
+ assert_string_equals(block, decoded, "UTF-16BE round trip " + block_tag);
+
+ encoded = TE_U8.encode(block);
+ decoded = TD_U8.decode(encoded);
+ assert_string_equals(block, decoded, "UTF-8 round trip " + block_tag);
+
+ // test TextEncoder(UTF-8) against the older idiom
+ var exp_encoded = encode_utf8(block);
+ assert_array_equals(encoded, exp_encoded,
+ "UTF-8 reference encoding " + block_tag);
+
+ var exp_decoded = decode_utf8(exp_encoded);
+ assert_string_equals(decoded, exp_decoded,
+ "UTF-8 reference decoding " + block_tag);
+ }
+}
+
+function test_utf_samples () {
+ // z, cent, CJK water, G-Clef, Private-use character
+ var sample = "z\xA2\u6C34\uD834\uDD1E\uDBFF\uDFFD";
+ var cases = [
+ { encoding: "utf-8",
+ expected: [0x7A, 0xC2, 0xA2, 0xE6, 0xB0, 0xB4, 0xF0, 0x9D, 0x84, 0x9E, 0xF4, 0x8F, 0xBF, 0xBD] },
+ { encoding: "utf-16le",
+ expected: [0x7A, 0x00, 0xA2, 0x00, 0x34, 0x6C, 0x34, 0xD8, 0x1E, 0xDD, 0xFF, 0xDB, 0xFD, 0xDF] },
+ { encoding: "utf-16",
+ expected: [0x7A, 0x00, 0xA2, 0x00, 0x34, 0x6C, 0x34, 0xD8, 0x1E, 0xDD, 0xFF, 0xDB, 0xFD, 0xDF] },
+ { encoding: "utf-16be",
+ expected: [0x00, 0x7A, 0x00, 0xA2, 0x6C, 0x34, 0xD8, 0x34, 0xDD, 0x1E, 0xDB, 0xFF, 0xDF, 0xFD] }
+ ];
+
+ cases.forEach(
+ function(t) {
+ var encoded = new TextEncoder(t.encoding).encode(sample);
+ assert_array_equals(encoded, t.expected,
+ "expected equal encodings - " + t.encoding);
+
+ var decoded = new TextDecoder(t.encoding)
+ .decode(new Uint8Array(t.expected));
+ assert_equals(decoded, sample,
+ "expected equal decodings - " + t.encoding);
+ });
+}
+
+test(test_utf_samples,
+ "UTF-8, UTF-16LE, UTF-16BE - Encode/Decode - reference sample");
+
+test(test_utf_roundtrip,
+ "UTF-8, UTF-16LE, UTF-16BE - Encode/Decode - full roundtrip and "+
+ "agreement with encode/decodeURIComponent");
diff --git a/test/test-x-user-defined.js b/test/test-x-user-defined.js
new file mode 100644
index 0000000..108005c
--- /dev/null
+++ b/test/test-x-user-defined.js
@@ -0,0 +1,29 @@
+// Copyright 2014 Joshua Bell. 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
+//
+// https://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.
+
+// NOTE: Requires testharness.js
+// http://www.w3.org/2008/webapps/wiki/Harness
+
+test(
+ function() {
+ assert_throws({name: 'RangeError'}, function() { new TextEncoder('x-user-defined'); });
+
+ var decoder = new TextDecoder('x-user-defined');
+ for (var i = 0; i < 0x80; ++i) {
+ assert_equals(decoder.decode(new Uint8Array([i])), String.fromCharCode(i));
+ assert_equals(decoder.decode(new Uint8Array([i + 0x80])), String.fromCharCode(i + 0xF780));
+ }
+ },
+ "x-user-defined encoding"
+);
diff --git a/test/testharness.css b/test/testharness.css
new file mode 100644
index 0000000..365084f
--- /dev/null
+++ b/test/testharness.css
@@ -0,0 +1,92 @@
+html {
+ font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
+}
+
+#log .warning,
+#log .warning a {
+ color: black;
+ background: yellow;
+}
+
+#log .error,
+#log .error a {
+ color: white;
+ background: red;
+}
+
+#log pre {
+ border: 1px solid black;
+ padding: 1em;
+}
+
+section#summary {
+ margin-bottom:1em;
+}
+
+table#results {
+ border-collapse:collapse;
+ table-layout:fixed;
+ width:100%;
+}
+
+table#results th:first-child,
+table#results td:first-child {
+ width:4em;
+}
+
+table#results th:last-child,
+table#results td:last-child {
+ width:50%;
+}
+
+table#results.assertions th:last-child,
+table#results.assertions td:last-child {
+ width:35%;
+}
+
+table#results th {
+ padding:0;
+ padding-bottom:0.5em;
+ border-bottom:medium solid black;
+}
+
+table#results td {
+ padding:1em;
+ padding-bottom:0.5em;
+ border-bottom:thin solid black;
+}
+
+tr.pass > td:first-child {
+ color:green;
+}
+
+tr.fail > td:first-child {
+ color:red;
+}
+
+tr.timeout > td:first-child {
+ color:red;
+}
+
+tr.notrun > td:first-child {
+ color:blue;
+}
+
+.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child {
+ font-variant:small-caps;
+}
+
+table#results span {
+ display:block;
+}
+
+table#results span.expected {
+ font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
+ white-space:pre;
+}
+
+table#results span.actual {
+ font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
+ white-space:pre;
+}
+
diff --git a/test/testharness.js b/test/testharness.js
new file mode 100644
index 0000000..4b2a147
--- /dev/null
+++ b/test/testharness.js
@@ -0,0 +1,2121 @@
+/*
+Distributed under both the W3C Test Suite License [1] and the W3C
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
+policies and contribution forms [3].
+
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
+[3] http://www.w3.org/2004/10/27-testcases
+*/
+
+/*
+ * == Introduction ==
+ *
+ * This file provides a framework for writing testcases. It is intended to
+ * provide a convenient API for making common assertions, and to work both
+ * for testing synchronous and asynchronous DOM features in a way that
+ * promotes clear, robust, tests.
+ *
+ * == Basic Usage ==
+ *
+ * To use this file, import the script and the testharnessreport script into
+ * the test document:
+ * <script src="/resources/testharness.js"></script>
+ * <script src="/resources/testharnessreport.js"></script>
+ *
+ * Within each file one may define one or more tests. Each test is atomic
+ * in the sense that a single test has a single result (pass/fail/timeout).
+ * Within each test one may have a number of asserts. The test fails at the
+ * first failing assert, and the remainder of the test is (typically) not run.
+ *
+ * If the file containing the tests is a HTML file with an element of id "log"
+ * this will be populated with a table containing the test results after all
+ * the tests have run.
+ *
+ * NOTE: By default tests must be created before the load event fires. For ways
+ * to create tests after the load event, see "Determining when all tests
+ * are complete", below
+ *
+ * == Synchronous Tests ==
+ *
+ * To create a synchronous test use the test() function:
+ *
+ * test(test_function, name, properties)
+ *
+ * test_function is a function that contains the code to test. For example a
+ * trivial passing test would be:
+ *
+ * test(function() {assert_true(true)}, "assert_true with true")
+ *
+ * The function passed in is run in the test() call.
+ *
+ * properties is an object that overrides default test properties. The
+ * recognised properties are:
+ * timeout - the test timeout in ms
+ *
+ * e.g.
+ * test(test_function, "Sample test", {timeout:1000})
+ *
+ * would run test_function with a timeout of 1s.
+ *
+ * Additionally, test-specific metadata can be passed in the properties. These
+ * are used when the individual test has different metadata from that stored
+ * in the <head>.
+ * The recognized metadata properties are:
+ *
+ * help - The url of the part of the specification being tested
+ *
+ * assert - A human readable description of what the test is attempting
+ * to prove
+ *
+ * author - Name and contact information for the author of the test in the
+ * format: "Name <email_addr>" or "Name https://contact/url"
+ *
+ * == Asynchronous Tests ==
+ *
+ * Testing asynchronous features is somewhat more complex since the result of
+ * a test may depend on one or more events or other callbacks. The API provided
+ * for testing these features is indended to be rather low-level but hopefully
+ * applicable to many situations.
+ *
+ * To create a test, one starts by getting a Test object using async_test:
+ *
+ * async_test(name, properties)
+ *
+ * e.g.
+ * var t = async_test("Simple async test")
+ *
+ * Assertions can be added to the test by calling the step method of the test
+ * object with a function containing the test assertions:
+ *
+ * t.step(function() {assert_true(true)});
+ *
+ * When all the steps are complete, the done() method must be called:
+ *
+ * t.done();
+ *
+ * The properties argument is identical to that for test().
+ *
+ * In many cases it is convenient to run a step in response to an event or a
+ * callback. A convenient method of doing this is through the step_func method
+ * which returns a function that, when called runs a test step. For example
+ *
+ * object.some_event = t.step_func(function(e) {assert_true(e.a)});
+ *
+ * == Making assertions ==
+ *
+ * Functions for making assertions start assert_
+ * The best way to get a list is to look in this file for functions names
+ * matching that pattern. The general signature is
+ *
+ * assert_something(actual, expected, description)
+ *
+ * although not all assertions precisely match this pattern e.g. assert_true
+ * only takes actual and description as arguments.
+ *
+ * The description parameter is used to present more useful error messages when
+ * a test fails
+ *
+ * NOTE: All asserts must be located in a test() or a step of an async_test().
+ * asserts outside these places won't be detected correctly by the harness
+ * and may cause a file to stop testing.
+ *
+ * == Setup ==
+ *
+ * Sometimes tests require non-trivial setup that may fail. For this purpose
+ * there is a setup() function, that may be called with one or two arguments.
+ * The two argument version is:
+ *
+ * setup(func, properties)
+ *
+ * The one argument versions may omit either argument.
+ * func is a function to be run synchronously. setup() becomes a no-op once
+ * any tests have returned results. Properties are global properties of the test
+ * harness. Currently recognised properties are:
+ *
+ * timeout - The time in ms after which the harness should stop waiting for
+ * tests to complete (this is different to the per-test timeout
+ * because async tests do not start their timer until .step is called)
+ *
+ * explicit_done - Wait for an explicit call to done() before declaring all
+ * tests complete (see below)
+ *
+ * output_document - The document to which results should be logged. By default
+ * this is the current document but could be an ancestor
+ * document in some cases e.g. a SVG test loaded in an HTML
+ * wrapper
+ *
+ * explicit_timeout - disable file timeout; only stop waiting for results
+ * when the timeout() function is called (typically for
+ * use when integrating with some existing test framework
+ * that has its own timeout mechanism).
+ *
+ * == Determining when all tests are complete ==
+ *
+ * By default the test harness will assume there are no more results to come
+ * when:
+ * 1) There are no Test objects that have been created but not completed
+ * 2) The load event on the document has fired
+ *
+ * This behaviour can be overridden by setting the explicit_done property to
+ * true in a call to setup(). If explicit_done is true, the test harness will
+ * not assume it is done until the global done() function is called. Once done()
+ * is called, the two conditions above apply like normal.
+ *
+ * == Generating tests ==
+ *
+ * NOTE: this functionality may be removed
+ *
+ * There are scenarios in which is is desirable to create a large number of
+ * (synchronous) tests that are internally similar but vary in the parameters
+ * used. To make this easier, the generate_tests function allows a single
+ * function to be called with each set of parameters in a list:
+ *
+ * generate_tests(test_function, parameter_lists, properties)
+ *
+ * For example:
+ *
+ * generate_tests(assert_equals, [
+ * ["Sum one and one", 1+1, 2],
+ * ["Sum one and zero", 1+0, 1]
+ * ])
+ *
+ * Is equivalent to:
+ *
+ * test(function() {assert_equals(1+1, 2)}, "Sum one and one")
+ * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
+ *
+ * Note that the first item in each parameter list corresponds to the name of
+ * the test.
+ *
+ * The properties argument is identical to that for test(). This may be a
+ * single object (used for all generated tests) or an array.
+ *
+ * == Callback API ==
+ *
+ * The framework provides callbacks corresponding to 3 events:
+ *
+ * start - happens when the first Test is created
+ * result - happens when a test result is recieved
+ * complete - happens when all results are recieved
+ *
+ * The page defining the tests may add callbacks for these events by calling
+ * the following methods:
+ *
+ * add_start_callback(callback) - callback called with no arguments
+ * add_result_callback(callback) - callback called with a test argument
+ * add_completion_callback(callback) - callback called with an array of tests
+ * and an status object
+ *
+ * tests have the following properties:
+ * status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
+ * NOTRUN properties on the test object
+ * message: A message indicating the reason for failure. In the future this
+ * will always be a string
+ *
+ * The status object gives the overall status of the harness. It has the
+ * following properties:
+ * status: Can be compared to the OK, ERROR and TIMEOUT properties
+ * message: An error message set when the status is ERROR
+ *
+ * == External API ==
+ *
+ * In order to collect the results of multiple pages containing tests, the test
+ * harness will, when loaded in a nested browsing context, attempt to call
+ * certain functions in each ancestor and opener browsing context:
+ *
+ * start - start_callback
+ * result - result_callback
+ * complete - completion_callback
+ *
+ * These are given the same arguments as the corresponding internal callbacks
+ * described above.
+ *
+ * == External API through cross-document messaging ==
+ *
+ * Where supported, the test harness will also send messages using
+ * cross-document messaging to each ancestor and opener browsing context. Since
+ * it uses the wildcard keyword (*), cross-origin communication is enabled and
+ * script on different origins can collect the results.
+ *
+ * This API follows similar conventions as those described above only slightly
+ * modified to accommodate message event API. Each message is sent by the harness
+ * is passed a single vanilla object, available as the `data` property of the
+ * event object. These objects are structures as follows:
+ *
+ * start - { type: "start" }
+ * result - { type: "result", test: Test }
+ * complete - { type: "complete", tests: [Test, ...], status: TestsStatus }
+ *
+ * == List of assertions ==
+ *
+ * assert_true(actual, description)
+ * asserts that /actual/ is strictly true
+ *
+ * assert_false(actual, description)
+ * asserts that /actual/ is strictly false
+ *
+ * assert_equals(actual, expected, description)
+ * asserts that /actual/ is the same value as /expected/
+ *
+ * assert_not_equals(actual, expected, description)
+ * asserts that /actual/ is a different value to /expected/. Yes, this means
+ * that "expected" is a misnomer
+ *
+ * assert_in_array(actual, expected, description)
+ * asserts that /expected/ is an Array, and /actual/ is equal to one of the
+ * members -- expected.indexOf(actual) != -1
+ *
+ * assert_array_equals(actual, expected, description)
+ * asserts that /actual/ and /expected/ have the same length and the value of
+ * each indexed property in /actual/ is the strictly equal to the corresponding
+ * property value in /expected/
+ *
+ * assert_approx_equals(actual, expected, epsilon, description)
+ * asserts that /actual/ is a number within +/- /epsilon/ of /expected/
+ *
+ * assert_regexp_match(actual, expected, description)
+ * asserts that /actual/ matches the regexp /expected/
+ *
+ * assert_class_string(object, class_name, description)
+ * asserts that the class string of /object/ as returned in
+ * Object.prototype.toString is equal to /class_name/.
+ *
+ * assert_own_property(object, property_name, description)
+ * assert that object has own property property_name
+ *
+ * assert_inherits(object, property_name, description)
+ * assert that object does not have an own property named property_name
+ * but that property_name is present in the prototype chain for object
+ *
+ * assert_idl_attribute(object, attribute_name, description)
+ * assert that an object that is an instance of some interface has the
+ * attribute attribute_name following the conditions specified by WebIDL
+ *
+ * assert_readonly(object, property_name, description)
+ * assert that property property_name on object is readonly
+ *
+ * assert_throws(code, func, description)
+ * code - the expected exception:
+ * o string: the thrown exception must be a DOMException with the given
+ * name, e.g., "TimeoutError" (for compatibility with existing
+ * tests, a constant is also supported, e.g., "TIMEOUT_ERR")
+ * o object: the thrown exception must have a property called "name" that
+ * matches code.name
+ * o null: allow any exception (in general, one of the options above
+ * should be used)
+ * func - a function that should throw
+ *
+ * assert_unreached(description)
+ * asserts if called. Used to ensure that some codepath is *not* taken e.g.
+ * an event does not fire.
+ *
+ * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N)
+ * asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N)
+ * is true for some expected_array_N in expected_array. This only works for assert_func
+ * with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests
+ * with multiple allowed pass conditions are bad practice unless the spec specifically
+ * allows multiple behaviours. Test authors should not use this method simply to hide
+ * UA bugs.
+ *
+ * assert_exists(object, property_name, description)
+ * *** deprecated ***
+ * asserts that object has an own property property_name
+ *
+ * assert_not_exists(object, property_name, description)
+ * *** deprecated ***
+ * assert that object does not have own property property_name
+ */
+
+(function ()
+{
+ var debug = false;
+ // default timeout is 5 seconds, test can override if needed
+ var settings = {
+ output:true,
+ timeout:5000,
+ test_timeout:2000
+ };
+
+ var xhtml_ns = "http://www.w3.org/1999/xhtml";
+
+ // script_prefix is used by Output.prototype.show_results() to figure out
+ // where to get testharness.css from. It's enclosed in an extra closure to
+ // not pollute the library's namespace with variables like "src".
+ var script_prefix = null;
+ (function ()
+ {
+ var scripts = document.getElementsByTagName("script");
+ for (var i = 0; i < scripts.length; i++)
+ {
+ if (scripts[i].src)
+ {
+ var src = scripts[i].src;
+ }
+ else if (scripts[i].href)
+ {
+ //SVG case
+ var src = scripts[i].href.baseVal;
+ }
+ if (src && src.slice(src.length - "testharness.js".length) === "testharness.js")
+ {
+ script_prefix = src.slice(0, src.length - "testharness.js".length);
+ break;
+ }
+ }
+ })();
+
+ /*
+ * API functions
+ */
+
+ var name_counter = 0;
+ function next_default_name()
+ {
+ //Don't use document.title to work around an Opera bug in XHTML documents
+ var prefix = document.getElementsByTagName("title").length > 0 ?
+ document.getElementsByTagName("title")[0].firstChild.data :
+ "Untitled";
+ var suffix = name_counter > 0 ? " " + name_counter : "";
+ name_counter++;
+ return prefix + suffix;
+ }
+
+ function test(func, name, properties)
+ {
+ var test_name = name ? name : next_default_name();
+ properties = properties ? properties : {};
+ var test_obj = new Test(test_name, properties);
+ test_obj.step(func);
+ if (test_obj.status === test_obj.NOTRUN) {
+ test_obj.done();
+ }
+ }
+
+ function async_test(name, properties)
+ {
+ var test_name = name ? name : next_default_name();
+ properties = properties ? properties : {};
+ var test_obj = new Test(test_name, properties);
+ return test_obj;
+ }
+
+ function setup(func_or_properties, maybe_properties)
+ {
+ var func = null;
+ var properties = {};
+ if (arguments.length === 2) {
+ func = func_or_properties;
+ properties = maybe_properties;
+ } else if (func_or_properties instanceof Function){
+ func = func_or_properties;
+ } else {
+ properties = func_or_properties;
+ }
+ tests.setup(func, properties);
+ output.setup(properties);
+ }
+
+ function done() {
+ tests.end_wait();
+ }
+
+ function generate_tests(func, args, properties) {
+ forEach(args, function(x, i)
+ {
+ var name = x[0];
+ test(function()
+ {
+ func.apply(this, x.slice(1));
+ },
+ name,
+ Array.isArray(properties) ? properties[i] : properties);
+ });
+ }
+
+ function on_event(object, event, callback)
+ {
+ object.addEventListener(event, callback, false);
+ }
+
+ expose(test, 'test');
+ expose(async_test, 'async_test');
+ expose(generate_tests, 'generate_tests');
+ expose(setup, 'setup');
+ expose(done, 'done');
+ expose(on_event, 'on_event');
+
+ /*
+ * Return a string truncated to the given length, with ... added at the end
+ * if it was longer.
+ */
+ function truncate(s, len)
+ {
+ if (s.length > len) {
+ return s.substring(0, len - 3) + "...";
+ }
+ return s;
+ }
+
+ /*
+ * Convert a value to a nice, human-readable string
+ */
+ function format_value(val)
+ {
+ if (Array.isArray(val))
+ {
+ return "[" + val.map(format_value).join(", ") + "]";
+ }
+
+ switch (typeof val)
+ {
+ case "string":
+ val = val.replace("\\", "\\\\");
+ for (var i = 0; i < 32; i++)
+ {
+ var replace = "\\";
+ switch (i) {
+ case 0: replace += "0"; break;
+ case 1: replace += "x01"; break;
+ case 2: replace += "x02"; break;
+ case 3: replace += "x03"; break;
+ case 4: replace += "x04"; break;
+ case 5: replace += "x05"; break;
+ case 6: replace += "x06"; break;
+ case 7: replace += "x07"; break;
+ case 8: replace += "b"; break;
+ case 9: replace += "t"; break;
+ case 10: replace += "n"; break;
+ case 11: replace += "v"; break;
+ case 12: replace += "f"; break;
+ case 13: replace += "r"; break;
+ case 14: replace += "x0e"; break;
+ case 15: replace += "x0f"; break;
+ case 16: replace += "x10"; break;
+ case 17: replace += "x11"; break;
+ case 18: replace += "x12"; break;
+ case 19: replace += "x13"; break;
+ case 20: replace += "x14"; break;
+ case 21: replace += "x15"; break;
+ case 22: replace += "x16"; break;
+ case 23: replace += "x17"; break;
+ case 24: replace += "x18"; break;
+ case 25: replace += "x19"; break;
+ case 26: replace += "x1a"; break;
+ case 27: replace += "x1b"; break;
+ case 28: replace += "x1c"; break;
+ case 29: replace += "x1d"; break;
+ case 30: replace += "x1e"; break;
+ case 31: replace += "x1f"; break;
+ }
+ val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
+ }
+ return '"' + val.replace(/"/g, '\\"') + '"';
+ case "boolean":
+ case "undefined":
+ return String(val);
+ case "number":
+ // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
+ // special-case.
+ if (val === -0 && 1/val === -Infinity)
+ {
+ return "-0";
+ }
+ return String(val);
+ case "object":
+ if (val === null)
+ {
+ return "null";
+ }
+
+ // Special-case Node objects, since those come up a lot in my tests. I
+ // ignore namespaces. I use duck-typing instead of instanceof, because
+ // instanceof doesn't work if the node is from another window (like an
+ // iframe's contentWindow):
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
+ if ("nodeType" in val
+ && "nodeName" in val
+ && "nodeValue" in val
+ && "childNodes" in val)
+ {
+ switch (val.nodeType)
+ {
+ case Node.ELEMENT_NODE:
+ var ret = "<" + val.tagName.toLowerCase();
+ for (var i = 0; i < val.attributes.length; i++)
+ {
+ ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
+ }
+ ret += ">" + val.innerHTML + "</" + val.tagName.toLowerCase() + ">";
+ return "Element node " + truncate(ret, 60);
+ case Node.TEXT_NODE:
+ return 'Text node "' + truncate(val.data, 60) + '"';
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
+ case Node.COMMENT_NODE:
+ return "Comment node <!--" + truncate(val.data, 60) + "-->";
+ case Node.DOCUMENT_NODE:
+ return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
+ case Node.DOCUMENT_TYPE_NODE:
+ return "DocumentType node";
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
+ default:
+ return "Node object of unknown type";
+ }
+ }
+
+ // Fall through to default
+ default:
+ return typeof val + ' "' + truncate(String(val), 60) + '"';
+ }
+ }
+ expose(format_value, "format_value");
+
+ /*
+ * Assertions
+ */
+
+ function assert_true(actual, description)
+ {
+ assert(actual === true, "assert_true", description,
+ "expected true got ${actual}", {actual:actual});
+ };
+ expose(assert_true, "assert_true");
+
+ function assert_false(actual, description)
+ {
+ assert(actual === false, "assert_false", description,
+ "expected false got ${actual}", {actual:actual});
+ };
+ expose(assert_false, "assert_false");
+
+ function same_value(x, y) {
+ if (y !== y)
+ {
+ //NaN case
+ return x !== x;
+ }
+ else if (x === 0 && y === 0) {
+ //Distinguish +0 and -0
+ return 1/x === 1/y;
+ }
+ else
+ {
+ //typical case
+ return x === y;
+ }
+ }
+
+ function assert_equals(actual, expected, description)
+ {
+ /*
+ * Test if two primitives are equal or two objects
+ * are the same object
+ */
+ if (typeof actual != typeof expected)
+ {
+ assert(false, "assert_equals", description,
+ "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
+ {expected:expected, actual:actual});
+ return;
+ }
+ assert(same_value(actual, expected), "assert_equals", description,
+ "expected ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ };
+ expose(assert_equals, "assert_equals");
+
+ function assert_not_equals(actual, expected, description)
+ {
+ /*
+ * Test if two primitives are unequal or two objects
+ * are different objects
+ */
+ assert(!same_value(actual, expected), "assert_not_equals", description,
+ "got disallowed value ${actual}",
+ {actual:actual});
+ };
+ expose(assert_not_equals, "assert_not_equals");
+
+ function assert_in_array(actual, expected, description)
+ {
+ assert(expected.indexOf(actual) != -1, "assert_in_array", description,
+ "value ${actual} not in array ${expected}",
+ {actual:actual, expected:expected});
+ }
+ expose(assert_in_array, "assert_in_array");
+
+ function assert_object_equals(actual, expected, description)
+ {
+ //This needs to be improved a great deal
+ function check_equal(actual, expected, stack)
+ {
+ stack.push(actual);
+
+ var p;
+ for (p in actual)
+ {
+ assert(expected.hasOwnProperty(p), "assert_object_equals", description,
+ "unexpected property ${p}", {p:p});
+
+ if (typeof actual[p] === "object" && actual[p] !== null)
+ {
+ if (stack.indexOf(actual[p]) === -1)
+ {
+ check_equal(actual[p], expected[p], stack);
+ }
+ }
+ else
+ {
+ assert(actual[p] === expected[p], "assert_object_equals", description,
+ "property ${p} expected ${expected} got ${actual}",
+ {p:p, expected:expected, actual:actual});
+ }
+ }
+ for (p in expected)
+ {
+ assert(actual.hasOwnProperty(p),
+ "assert_object_equals", description,
+ "expected property ${p} missing", {p:p});
+ }
+ stack.pop();
+ }
+ check_equal(actual, expected, []);
+ };
+ expose(assert_object_equals, "assert_object_equals");
+
+ function assert_array_equals(actual, expected, description)
+ {
+ assert(actual.length === expected.length,
+ "assert_array_equals", description,
+ "lengths differ, expected ${expected} got ${actual}",
+ {expected:expected.length, actual:actual.length});
+
+ for (var i=0; i < actual.length; i++)
+ {
+ assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
+ "assert_array_equals", description,
+ "property ${i}, property expected to be $expected but was $actual",
+ {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
+ actual:actual.hasOwnProperty(i) ? "present" : "missing"});
+ assert(expected[i] === actual[i],
+ "assert_array_equals", description,
+ "property ${i}, expected ${expected} but got ${actual}",
+ {i:i, expected:expected[i], actual:actual[i]});
+ }
+ }
+ expose(assert_array_equals, "assert_array_equals");
+
+ function assert_approx_equals(actual, expected, epsilon, description)
+ {
+ /*
+ * Test if two primitive numbers are equal withing +/- epsilon
+ */
+ assert(typeof actual === "number",
+ "assert_approx_equals", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(Math.abs(actual - expected) <= epsilon,
+ "assert_approx_equals", description,
+ "expected ${expected} +/- ${epsilon} but got ${actual}",
+ {expected:expected, actual:actual, epsilon:epsilon});
+ };
+ expose(assert_approx_equals, "assert_approx_equals");
+
+ function assert_regexp_match(actual, expected, description) {
+ /*
+ * Test if a string (actual) matches a regexp (expected)
+ */
+ assert(expected.test(actual),
+ "assert_regexp_match", description,
+ "expected ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ }
+ expose(assert_regexp_match, "assert_regexp_match");
+
+ function assert_class_string(object, class_string, description) {
+ assert_equals({}.toString.call(object), "[object " + class_string + "]",
+ description);
+ }
+ expose(assert_class_string, "assert_class_string");
+
+
+ function _assert_own_property(name) {
+ return function(object, property_name, description)
+ {
+ assert(object.hasOwnProperty(property_name),
+ name, description,
+ "expected property ${p} missing", {p:property_name});
+ };
+ }
+ expose(_assert_own_property("assert_exists"), "assert_exists");
+ expose(_assert_own_property("assert_own_property"), "assert_own_property");
+
+ function assert_not_exists(object, property_name, description)
+ {
+ assert(!object.hasOwnProperty(property_name),
+ "assert_not_exists", description,
+ "unexpected property ${p} found", {p:property_name});
+ };
+ expose(assert_not_exists, "assert_not_exists");
+
+ function _assert_inherits(name) {
+ return function (object, property_name, description)
+ {
+ assert(typeof object === "object",
+ name, description,
+ "provided value is not an object");
+
+ assert("hasOwnProperty" in object,
+ name, description,
+ "provided value is an object but has no hasOwnProperty method");
+
+ assert(!object.hasOwnProperty(property_name),
+ name, description,
+ "property ${p} found on object expected in prototype chain",
+ {p:property_name});
+
+ assert(property_name in object,
+ name, description,
+ "property ${p} not found in prototype chain",
+ {p:property_name});
+ };
+ }
+ expose(_assert_inherits("assert_inherits"), "assert_inherits");
+ expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
+
+ function assert_readonly(object, property_name, description)
+ {
+ var initial_value = object[property_name];
+ try {
+ //Note that this can have side effects in the case where
+ //the property has PutForwards
+ object[property_name] = initial_value + "a"; //XXX use some other value here?
+ assert(object[property_name] === initial_value,
+ "assert_readonly", description,
+ "changing property ${p} succeeded",
+ {p:property_name});
+ }
+ finally
+ {
+ object[property_name] = initial_value;
+ }
+ };
+ expose(assert_readonly, "assert_readonly");
+
+ function assert_throws(code, func, description)
+ {
+ try
+ {
+ func.call(this);
+ assert(false, "assert_throws", description,
+ "${func} did not throw", {func:func});
+ }
+ catch(e)
+ {
+ if (e instanceof AssertionError) {
+ throw(e);
+ }
+ if (code === null)
+ {
+ return;
+ }
+ if (typeof code === "object")
+ {
+ assert(typeof e == "object" && "name" in e && e.name == code.name,
+ "assert_throws", description,
+ "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
+ {func:func, actual:e, actual_name:e.name,
+ expected:code,
+ expected_name:code.name});
+ return;
+ }
+
+ var code_name_map = {
+ INDEX_SIZE_ERR: 'IndexSizeError',
+ HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
+ WRONG_DOCUMENT_ERR: 'WrongDocumentError',
+ INVALID_CHARACTER_ERR: 'InvalidCharacterError',
+ NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
+ NOT_FOUND_ERR: 'NotFoundError',
+ NOT_SUPPORTED_ERR: 'NotSupportedError',
+ INVALID_STATE_ERR: 'InvalidStateError',
+ SYNTAX_ERR: 'SyntaxError',
+ INVALID_MODIFICATION_ERR: 'InvalidModificationError',
+ NAMESPACE_ERR: 'NamespaceError',
+ INVALID_ACCESS_ERR: 'InvalidAccessError',
+ TYPE_MISMATCH_ERR: 'TypeMismatchError',
+ SECURITY_ERR: 'SecurityError',
+ NETWORK_ERR: 'NetworkError',
+ ABORT_ERR: 'AbortError',
+ URL_MISMATCH_ERR: 'URLMismatchError',
+ QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
+ TIMEOUT_ERR: 'TimeoutError',
+ INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
+ DATA_CLONE_ERR: 'DataCloneError',
+ };
+
+ var name = code in code_name_map ? code_name_map[code] : code;
+
+ var name_code_map = {
+ IndexSizeError: 1,
+ HierarchyRequestError: 3,
+ WrongDocumentError: 4,
+ InvalidCharacterError: 5,
+ NoModificationAllowedError: 7,
+ NotFoundError: 8,
+ NotSupportedError: 9,
+ InvalidStateError: 11,
+ SyntaxError: 12,
+ InvalidModificationError: 13,
+ NamespaceError: 14,
+ InvalidAccessError: 15,
+ TypeMismatchError: 17,
+ SecurityError: 18,
+ NetworkError: 19,
+ AbortError: 20,
+ URLMismatchError: 21,
+ QuotaExceededError: 22,
+ TimeoutError: 23,
+ InvalidNodeTypeError: 24,
+ DataCloneError: 25,
+
+ UnknownError: 0,
+ ConstraintError: 0,
+ DataError: 0,
+ TransactionInactiveError: 0,
+ ReadOnlyError: 0,
+ VersionError: 0,
+ };
+
+ if (!(name in name_code_map))
+ {
+ throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
+ }
+
+ var required_props = { code: name_code_map[name] };
+
+ if (required_props.code === 0
+ || ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException"))
+ {
+ // New style exception: also test the name property.
+ required_props.name = name;
+ }
+
+ //We'd like to test that e instanceof the appropriate interface,
+ //but we can't, because we don't know what window it was created
+ //in. It might be an instanceof the appropriate interface on some
+ //unknown other window. TODO: Work around this somehow?
+
+ assert(typeof e == "object",
+ "assert_throws", description,
+ "${func} threw ${e} with type ${type}, not an object",
+ {func:func, e:e, type:typeof e});
+
+ for (var prop in required_props)
+ {
+ assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
+ "assert_throws", description,
+ "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
+ {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
+ }
+ }
+ }
+ expose(assert_throws, "assert_throws");
+
+ function assert_unreached(description) {
+ assert(false, "assert_unreached", description,
+ "Reached unreachable code");
+ }
+ expose(assert_unreached, "assert_unreached");
+
+ function assert_any(assert_func, actual, expected_array)
+ {
+ var args = [].slice.call(arguments, 3)
+ var errors = []
+ var passed = false;
+ forEach(expected_array,
+ function(expected)
+ {
+ try {
+ assert_func.apply(this, [actual, expected].concat(args))
+ passed = true;
+ } catch(e) {
+ errors.push(e.message);
+ }
+ });
+ if (!passed) {
+ throw new AssertionError(errors.join("\n\n"));
+ }
+ }
+ expose(assert_any, "assert_any");
+
+ function Test(name, properties)
+ {
+ this.name = name;
+ this.status = this.NOTRUN;
+ this.timeout_id = null;
+ this.is_done = false;
+
+ this.properties = properties;
+ this.timeout_length = properties.timeout ? properties.timeout : settings.test_timeout;
+
+ this.message = null;
+
+ var this_obj = this;
+ this.steps = [];
+
+ tests.push(this);
+ }
+
+ Test.statuses = {
+ PASS:0,
+ FAIL:1,
+ TIMEOUT:2,
+ NOTRUN:3
+ };
+
+ Test.prototype = merge({}, Test.statuses);
+
+ Test.prototype.structured_clone = function()
+ {
+ if(!this._structured_clone)
+ {
+ var msg = this.message;
+ msg = msg ? String(msg) : msg;
+ this._structured_clone = merge({
+ name:String(this.name),
+ status:this.status,
+ message:msg
+ }, Test.statuses);
+ }
+ return this._structured_clone;
+ };
+
+ Test.prototype.step = function(func, this_obj)
+ {
+ //In case the test has already failed
+ if (this.status !== this.NOTRUN)
+ {
+ return;
+ }
+
+ tests.started = true;
+
+ if (this.timeout_id === null) {
+ this.set_timeout();
+ }
+
+ this.steps.push(func);
+
+ if (arguments.length === 1)
+ {
+ this_obj = this;
+ }
+
+ try
+ {
+ func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
+ }
+ catch(e)
+ {
+ //This can happen if something called synchronously invoked another
+ //step
+ if (this.status !== this.NOTRUN)
+ {
+ return;
+ }
+ this.status = this.FAIL;
+ this.message = (typeof e === "object" && e !== null) ? e.message : e;
+ if (typeof e.stack != "undefined" && typeof e.message == "string") {
+ //Try to make it more informative for some exceptions, at least
+ //in Gecko and WebKit. This results in a stack dump instead of
+ //just errors like "Cannot read property 'parentNode' of null"
+ //or "root is null". Makes it a lot longer, of course.
+ this.message += "(stack: " + e.stack + ")";
+ }
+ this.done();
+ if (debug && e.constructor !== AssertionError) {
+ throw e;
+ }
+ }
+ };
+
+ Test.prototype.step_func = function(func, this_obj)
+ {
+ var test_this = this;
+
+ if (arguments.length === 1)
+ {
+ this_obj = test_this;
+ }
+
+ return function()
+ {
+ test_this.step.apply(test_this, [func, this_obj].concat(
+ Array.prototype.slice.call(arguments)));
+ };
+ };
+
+ Test.prototype.step_func_done = function(func, this_obj)
+ {
+ var test_this = this;
+
+ if (arguments.length === 1)
+ {
+ this_obj = test_this;
+ }
+
+ return function()
+ {
+ test_this.step.apply(test_this, [func, this_obj].concat(
+ Array.prototype.slice.call(arguments)));
+ test_this.done();
+ };
+ };
+
+ Test.prototype.set_timeout = function()
+ {
+ var this_obj = this;
+ this.timeout_id = setTimeout(function()
+ {
+ this_obj.timeout();
+ }, this.timeout_length);
+ };
+
+ Test.prototype.timeout = function()
+ {
+ this.status = this.TIMEOUT;
+ this.timeout_id = null;
+ this.message = "Test timed out";
+ this.done();
+ };
+
+ Test.prototype.done = function()
+ {
+ if (this.is_done) {
+ return;
+ }
+ clearTimeout(this.timeout_id);
+ if (this.status === this.NOTRUN)
+ {
+ this.status = this.PASS;
+ }
+ this.is_done = true;
+ tests.result(this);
+ };
+
+
+ /*
+ * Harness
+ */
+
+ function TestsStatus()
+ {
+ this.status = null;
+ this.message = null;
+ }
+
+ TestsStatus.statuses = {
+ OK:0,
+ ERROR:1,
+ TIMEOUT:2
+ };
+
+ TestsStatus.prototype = merge({}, TestsStatus.statuses);
+
+ TestsStatus.prototype.structured_clone = function()
+ {
+ if(!this._structured_clone)
+ {
+ var msg = this.message;
+ msg = msg ? String(msg) : msg;
+ this._structured_clone = merge({
+ status:this.status,
+ message:msg
+ }, TestsStatus.statuses);
+ }
+ return this._structured_clone;
+ };
+
+ function Tests()
+ {
+ this.tests = [];
+ this.num_pending = 0;
+
+ this.phases = {
+ INITIAL:0,
+ SETUP:1,
+ HAVE_TESTS:2,
+ HAVE_RESULTS:3,
+ COMPLETE:4
+ };
+ this.phase = this.phases.INITIAL;
+
+ this.properties = {};
+
+ //All tests can't be done until the load event fires
+ this.all_loaded = false;
+ this.wait_for_finish = false;
+ this.processing_callbacks = false;
+
+ this.timeout_length = settings.timeout;
+ this.timeout_id = null;
+
+ this.start_callbacks = [];
+ this.test_done_callbacks = [];
+ this.all_done_callbacks = [];
+
+ this.status = new TestsStatus();
+
+ var this_obj = this;
+
+ on_event(window, "load",
+ function()
+ {
+ this_obj.all_loaded = true;
+ if (this_obj.all_done())
+ {
+ this_obj.complete();
+ }
+ });
+
+ this.set_timeout();
+ }
+
+ Tests.prototype.setup = function(func, properties)
+ {
+ if (this.phase >= this.phases.HAVE_RESULTS)
+ {
+ return;
+ }
+ if (this.phase < this.phases.SETUP)
+ {
+ this.phase = this.phases.SETUP;
+ }
+
+ for (var p in properties)
+ {
+ if (properties.hasOwnProperty(p))
+ {
+ this.properties[p] = properties[p];
+ }
+ }
+
+ if (properties.timeout)
+ {
+ this.timeout_length = properties.timeout;
+ }
+ if (properties.explicit_done)
+ {
+ this.wait_for_finish = true;
+ }
+ if (properties.explicit_timeout) {
+ this.timeout_length = null;
+ }
+
+ if (func)
+ {
+ try
+ {
+ func();
+ } catch(e)
+ {
+ this.status.status = this.status.ERROR;
+ this.status.message = e;
+ };
+ }
+ this.set_timeout();
+ };
+
+ Tests.prototype.set_timeout = function()
+ {
+ var this_obj = this;
+ clearTimeout(this.timeout_id);
+ if (this.timeout_length !== null)
+ {
+ this.timeout_id = setTimeout(function() {
+ this_obj.timeout();
+ }, this.timeout_length);
+ }
+ };
+
+ Tests.prototype.timeout = function() {
+ this.status.status = this.status.TIMEOUT;
+ this.complete();
+ };
+
+ Tests.prototype.end_wait = function()
+ {
+ this.wait_for_finish = false;
+ if (this.all_done()) {
+ this.complete();
+ }
+ };
+
+ Tests.prototype.push = function(test)
+ {
+ if (this.phase < this.phases.HAVE_TESTS) {
+ this.start();
+ }
+ this.num_pending++;
+ this.tests.push(test);
+ };
+
+ Tests.prototype.all_done = function() {
+ return (this.all_loaded && this.num_pending === 0 &&
+ !this.wait_for_finish && !this.processing_callbacks);
+ };
+
+ Tests.prototype.start = function() {
+ this.phase = this.phases.HAVE_TESTS;
+ this.notify_start();
+ };
+
+ Tests.prototype.notify_start = function() {
+ var this_obj = this;
+ forEach (this.start_callbacks,
+ function(callback)
+ {
+ callback(this_obj.properties);
+ });
+ forEach_windows(
+ function(w, is_same_origin)
+ {
+ if(is_same_origin && w.start_callback)
+ {
+ try
+ {
+ w.start_callback(this_obj.properties);
+ }
+ catch(e)
+ {
+ if (debug)
+ {
+ throw(e);
+ }
+ }
+ }
+ if (supports_post_message(w))
+ {
+ w.postMessage({
+ type: "start",
+ properties: this_obj.properties
+ }, "*");
+ }
+ });
+ };
+
+ Tests.prototype.result = function(test)
+ {
+ if (this.phase > this.phases.HAVE_RESULTS)
+ {
+ return;
+ }
+ this.phase = this.phases.HAVE_RESULTS;
+ this.num_pending--;
+ this.notify_result(test);
+ };
+
+ Tests.prototype.notify_result = function(test) {
+ var this_obj = this;
+ this.processing_callbacks = true;
+ forEach(this.test_done_callbacks,
+ function(callback)
+ {
+ callback(test, this_obj);
+ });
+
+ forEach_windows(
+ function(w, is_same_origin)
+ {
+ if(is_same_origin && w.result_callback)
+ {
+ try
+ {
+ w.result_callback(test);
+ }
+ catch(e)
+ {
+ if(debug) {
+ throw e;
+ }
+ }
+ }
+ if (supports_post_message(w))
+ {
+ w.postMessage({
+ type: "result",
+ test: test.structured_clone()
+ }, "*");
+ }
+ });
+ this.processing_callbacks = false;
+ if (this_obj.all_done())
+ {
+ this_obj.complete();
+ }
+ };
+
+ Tests.prototype.complete = function() {
+ if (this.phase === this.phases.COMPLETE) {
+ return;
+ }
+ this.phase = this.phases.COMPLETE;
+ var this_obj = this;
+ this.tests.forEach(
+ function(x)
+ {
+ if(x.status === x.NOTRUN)
+ {
+ this_obj.notify_result(x);
+ }
+ }
+ );
+ this.notify_complete();
+ };
+
+ Tests.prototype.notify_complete = function()
+ {
+ clearTimeout(this.timeout_id);
+ var this_obj = this;
+ var tests = map(this_obj.tests,
+ function(test)
+ {
+ return test.structured_clone();
+ });
+ if (this.status.status === null)
+ {
+ this.status.status = this.status.OK;
+ }
+
+ forEach (this.all_done_callbacks,
+ function(callback)
+ {
+ callback(this_obj.tests, this_obj.status);
+ });
+
+ forEach_windows(
+ function(w, is_same_origin)
+ {
+ if(is_same_origin && w.completion_callback)
+ {
+ try
+ {
+ w.completion_callback(this_obj.tests, this_obj.status);
+ }
+ catch(e)
+ {
+ if (debug)
+ {
+ throw e;
+ }
+ }
+ }
+ if (supports_post_message(w))
+ {
+ w.postMessage({
+ type: "complete",
+ tests: tests,
+ status: this_obj.status.structured_clone()
+ }, "*");
+ }
+ });
+ };
+
+ var tests = new Tests();
+
+ function timeout() {
+ if (tests.timeout_length === null)
+ {
+ tests.timeout();
+ }
+ }
+ expose(timeout, 'timeout');
+
+ function add_start_callback(callback) {
+ tests.start_callbacks.push(callback);
+ }
+
+ function add_result_callback(callback)
+ {
+ tests.test_done_callbacks.push(callback);
+ }
+
+ function add_completion_callback(callback)
+ {
+ tests.all_done_callbacks.push(callback);
+ }
+
+ expose(add_start_callback, 'add_start_callback');
+ expose(add_result_callback, 'add_result_callback');
+ expose(add_completion_callback, 'add_completion_callback');
+
+ /*
+ * Output listener
+ */
+
+ function Output() {
+ this.output_document = null;
+ this.output_node = null;
+ this.done_count = 0;
+ this.enabled = settings.output;
+ this.phase = this.INITIAL;
+ }
+
+ Output.prototype.INITIAL = 0;
+ Output.prototype.STARTED = 1;
+ Output.prototype.HAVE_RESULTS = 2;
+ Output.prototype.COMPLETE = 3;
+
+ Output.prototype.setup = function(properties) {
+ if (this.phase > this.INITIAL) {
+ return;
+ }
+
+ //If output is disabled in testharnessreport.js the test shouldn't be
+ //able to override that
+ this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
+ properties.output : settings.output);
+ };
+
+ Output.prototype.init = function(properties)
+ {
+ if (this.phase >= this.STARTED) {
+ return;
+ }
+ if (properties.output_document) {
+ this.output_document = properties.output_document;
+ } else {
+ this.output_document = document;
+ }
+ this.phase = this.STARTED;
+ };
+
+ Output.prototype.resolve_log = function()
+ {
+ var output_document;
+ if (typeof this.output_document === "function")
+ {
+ output_document = this.output_document.apply(undefined);
+ } else
+ {
+ output_document = this.output_document;
+ }
+ if (!output_document)
+ {
+ return;
+ }
+ var node = output_document.getElementById("log");
+ if (node)
+ {
+ this.output_document = output_document;
+ this.output_node = node;
+ }
+ };
+
+ Output.prototype.show_status = function(test)
+ {
+ if (this.phase < this.STARTED)
+ {
+ this.init();
+ }
+ if (!this.enabled)
+ {
+ return;
+ }
+ if (this.phase < this.HAVE_RESULTS)
+ {
+ this.resolve_log();
+ this.phase = this.HAVE_RESULTS;
+ }
+ this.done_count++;
+ if (this.output_node)
+ {
+ if (this.done_count < 100
+ || (this.done_count < 1000 && this.done_count % 100 == 0)
+ || this.done_count % 1000 == 0) {
+ this.output_node.textContent = "Running, "
+ + this.done_count + " complete, "
+ + tests.num_pending + " remain";
+ }
+ }
+ };
+
+ Output.prototype.show_results = function (tests, harness_status)
+ {
+ if (this.phase >= this.COMPLETE) {
+ return;
+ }
+ if (!this.enabled)
+ {
+ return;
+ }
+ if (!this.output_node) {
+ this.resolve_log();
+ }
+ this.phase = this.COMPLETE;
+
+ var log = this.output_node;
+ if (!log)
+ {
+ return;
+ }
+ var output_document = this.output_document;
+
+ while (log.lastChild)
+ {
+ log.removeChild(log.lastChild);
+ }
+
+ if (script_prefix != null) {
+ var stylesheet = output_document.createElementNS(xhtml_ns, "link");
+ stylesheet.setAttribute("rel", "stylesheet");
+ stylesheet.setAttribute("href", script_prefix + "testharness.css");
+ var heads = output_document.getElementsByTagName("head");
+ if (heads.length) {
+ heads[0].appendChild(stylesheet);
+ }
+ }
+
+ var status_text = {};
+ status_text[Test.prototype.PASS] = "Pass";
+ status_text[Test.prototype.FAIL] = "Fail";
+ status_text[Test.prototype.TIMEOUT] = "Timeout";
+ status_text[Test.prototype.NOTRUN] = "Not Run";
+
+ var status_number = {};
+ forEach(tests, function(test) {
+ var status = status_text[test.status];
+ if (status_number.hasOwnProperty(status))
+ {
+ status_number[status] += 1;
+ } else {
+ status_number[status] = 1;
+ }
+ });
+
+ function status_class(status)
+ {
+ return status.replace(/\s/g, '').toLowerCase();
+ }
+
+ var summary_template = ["section", {"id":"summary"},
+ ["h2", {}, "Summary"],
+ ["p", {}, "Found ${num_tests} tests"],
+ function(vars) {
+ var rv = [["div", {}]];
+ var i=0;
+ while (status_text.hasOwnProperty(i)) {
+ if (status_number.hasOwnProperty(status_text[i])) {
+ var status = status_text[i];
+ rv[0].push(["div", {"class":status_class(status)},
+ ["label", {},
+ ["input", {type:"checkbox", checked:"checked"}],
+ status_number[status] + " " + status]]);
+ }
+ i++;
+ }
+ return rv;
+ }];
+
+ log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
+
+ forEach(output_document.querySelectorAll("section#summary label"),
+ function(element)
+ {
+ on_event(element, "click",
+ function(e)
+ {
+ if (output_document.getElementById("results") === null)
+ {
+ e.preventDefault();
+ return;
+ }
+ var result_class = element.parentNode.getAttribute("class");
+ var style_element = output_document.querySelector("style#hide-" + result_class);
+ var input_element = element.querySelector("input");
+ if (!style_element && !input_element.checked) {
+ style_element = output_document.createElementNS(xhtml_ns, "style");
+ style_element.id = "hide-" + result_class;
+ style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
+ output_document.body.appendChild(style_element);
+ } else if (style_element && input_element.checked) {
+ style_element.parentNode.removeChild(style_element);
+ }
+ });
+ });
+
+ // This use of innerHTML plus manual escaping is not recommended in
+ // general, but is necessary here for performance. Using textContent
+ // on each individual <td> adds tens of seconds of execution time for
+ // large test suites (tens of thousands of tests).
+ function escape_html(s)
+ {
+ return s.replace(/\&/g, "&")
+ .replace(/</g, "<")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ }
+
+ function has_assertions()
+ {
+ for (var i = 0; i < tests.length; i++) {
+ if (tests[i].properties.hasOwnProperty("assert")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function get_assertion(test)
+ {
+ if (test.properties.hasOwnProperty("assert")) {
+ if (Array.isArray(test.properties.assert)) {
+ return test.properties.assert.join(' ');
+ }
+ return test.properties.assert;
+ }
+ return '';
+ }
+
+ log.appendChild(document.createElementNS(xhtml_ns, "section"));
+ var assertions = has_assertions();
+ var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">"
+ + "<thead><tr><th>Result</th><th>Test Name</th>"
+ + (assertions ? "<th>Assertion</th>" : "")
+ + "<th>Message</th></tr></thead>"
+ + "<tbody>";
+ for (var i = 0; i < tests.length; i++) {
+ html += '<tr class="'
+ + escape_html(status_class(status_text[tests[i].status]))
+ + '"><td>'
+ + escape_html(status_text[tests[i].status])
+ + "</td><td>"
+ + escape_html(tests[i].name)
+ + "</td><td>"
+ + (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "")
+ + escape_html(tests[i].message ? tests[i].message : " ")
+ + "</td></tr>";
+ }
+ html += "</tbody></table>";
+ try {
+ log.lastChild.innerHTML = html;
+ } catch (e) {
+ log.appendChild(document.createElementNS(xhtml_ns, "p"))
+ .textContent = "Setting innerHTML for the log threw an exception.";
+ log.appendChild(document.createElementNS(xhtml_ns, "pre"))
+ .textContent = html;
+ }
+ };
+
+ var output = new Output();
+ add_start_callback(function (properties) {output.init(properties);});
+ add_result_callback(function (test) {output.show_status(tests);});
+ add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});
+
+ /*
+ * Template code
+ *
+ * A template is just a javascript structure. An element is represented as:
+ *
+ * [tag_name, {attr_name:attr_value}, child1, child2]
+ *
+ * the children can either be strings (which act like text nodes), other templates or
+ * functions (see below)
+ *
+ * A text node is represented as
+ *
+ * ["{text}", value]
+ *
+ * String values have a simple substitution syntax; ${foo} represents a variable foo.
+ *
+ * It is possible to embed logic in templates by using a function in a place where a
+ * node would usually go. The function must either return part of a template or null.
+ *
+ * In cases where a set of nodes are required as output rather than a single node
+ * with children it is possible to just use a list
+ * [node1, node2, node3]
+ *
+ * Usage:
+ *
+ * render(template, substitutions) - take a template and an object mapping
+ * variable names to parameters and return either a DOM node or a list of DOM nodes
+ *
+ * substitute(template, substitutions) - take a template and variable mapping object,
+ * make the variable substitutions and return the substituted template
+ *
+ */
+
+ function is_single_node(template)
+ {
+ return typeof template[0] === "string";
+ }
+
+ function substitute(template, substitutions)
+ {
+ if (typeof template === "function") {
+ var replacement = template(substitutions);
+ if (replacement)
+ {
+ var rv = substitute(replacement, substitutions);
+ return rv;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else if (is_single_node(template))
+ {
+ return substitute_single(template, substitutions);
+ }
+ else
+ {
+ return filter(map(template, function(x) {
+ return substitute(x, substitutions);
+ }), function(x) {return x !== null;});
+ }
+ }
+
+ function substitute_single(template, substitutions)
+ {
+ var substitution_re = /\${([^ }]*)}/g;
+
+ function do_substitution(input) {
+ var components = input.split(substitution_re);
+ var rv = [];
+ for (var i=0; i<components.length; i+=2)
+ {
+ rv.push(components[i]);
+ if (components[i+1])
+ {
+ rv.push(String(substitutions[components[i+1]]));
+ }
+ }
+ return rv;
+ }
+
+ var rv = [];
+ rv.push(do_substitution(String(template[0])).join(""));
+
+ if (template[0] === "{text}") {
+ substitute_children(template.slice(1), rv);
+ } else {
+ substitute_attrs(template[1], rv);
+ substitute_children(template.slice(2), rv);
+ }
+
+ function substitute_attrs(attrs, rv)
+ {
+ rv[1] = {};
+ for (var name in template[1])
+ {
+ if (attrs.hasOwnProperty(name))
+ {
+ var new_name = do_substitution(name).join("");
+ var new_value = do_substitution(attrs[name]).join("");
+ rv[1][new_name] = new_value;
+ };
+ }
+ }
+
+ function substitute_children(children, rv)
+ {
+ for (var i=0; i<children.length; i++)
+ {
+ if (children[i] instanceof Object) {
+ var replacement = substitute(children[i], substitutions);
+ if (replacement !== null)
+ {
+ if (is_single_node(replacement))
+ {
+ rv.push(replacement);
+ }
+ else
+ {
+ extend(rv, replacement);
+ }
+ }
+ }
+ else
+ {
+ extend(rv, do_substitution(String(children[i])));
+ }
+ }
+ return rv;
+ }
+
+ return rv;
+ }
+
+ function make_dom_single(template, doc)
+ {
+ var output_document = doc || document;
+ if (template[0] === "{text}")
+ {
+ var element = output_document.createTextNode("");
+ for (var i=1; i<template.length; i++)
+ {
+ element.data += template[i];
+ }
+ }
+ else
+ {
+ var element = output_document.createElementNS(xhtml_ns, template[0]);
+ for (var name in template[1]) {
+ if (template[1].hasOwnProperty(name))
+ {
+ element.setAttribute(name, template[1][name]);
+ }
+ }
+ for (var i=2; i<template.length; i++)
+ {
+ if (template[i] instanceof Object)
+ {
+ var sub_element = make_dom(template[i]);
+ element.appendChild(sub_element);
+ }
+ else
+ {
+ var text_node = output_document.createTextNode(template[i]);
+ element.appendChild(text_node);
+ }
+ }
+ }
+
+ return element;
+ }
+
+
+
+ function make_dom(template, substitutions, output_document)
+ {
+ if (is_single_node(template))
+ {
+ return make_dom_single(template, output_document);
+ }
+ else
+ {
+ return map(template, function(x) {
+ return make_dom_single(x, output_document);
+ });
+ }
+ }
+
+ function render(template, substitutions, output_document)
+ {
+ return make_dom(substitute(template, substitutions), output_document);
+ }
+
+ /*
+ * Utility funcions
+ */
+ function assert(expected_true, function_name, description, error, substitutions)
+ {
+ if (expected_true !== true)
+ {
+ throw new AssertionError(make_message(function_name, description,
+ error, substitutions));
+ }
+ }
+
+ function AssertionError(message)
+ {
+ this.message = message;
+ }
+
+ function make_message(function_name, description, error, substitutions)
+ {
+ for (var p in substitutions) {
+ if (substitutions.hasOwnProperty(p)) {
+ substitutions[p] = format_value(substitutions[p]);
+ }
+ }
+ var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
+ merge({function_name:function_name,
+ description:(description?description + " ":"")},
+ substitutions));
+ return node_form.slice(1).join("");
+ }
+
+ function filter(array, callable, thisObj) {
+ var rv = [];
+ for (var i=0; i<array.length; i++)
+ {
+ if (array.hasOwnProperty(i))
+ {
+ var pass = callable.call(thisObj, array[i], i, array);
+ if (pass) {
+ rv.push(array[i]);
+ }
+ }
+ }
+ return rv;
+ }
+
+ function map(array, callable, thisObj)
+ {
+ var rv = [];
+ rv.length = array.length;
+ for (var i=0; i<array.length; i++)
+ {
+ if (array.hasOwnProperty(i))
+ {
+ rv[i] = callable.call(thisObj, array[i], i, array);
+ }
+ }
+ return rv;
+ }
+
+ function extend(array, items)
+ {
+ Array.prototype.push.apply(array, items);
+ }
+
+ function forEach (array, callback, thisObj)
+ {
+ for (var i=0; i<array.length; i++)
+ {
+ if (array.hasOwnProperty(i))
+ {
+ callback.call(thisObj, array[i], i, array);
+ }
+ }
+ }
+
+ function merge(a,b)
+ {
+ var rv = {};
+ var p;
+ for (p in a)
+ {
+ rv[p] = a[p];
+ }
+ for (p in b) {
+ rv[p] = b[p];
+ }
+ return rv;
+ }
+
+ function expose(object, name)
+ {
+ var components = name.split(".");
+ var target = window;
+ for (var i=0; i<components.length - 1; i++)
+ {
+ if (!(components[i] in target))
+ {
+ target[components[i]] = {};
+ }
+ target = target[components[i]];
+ }
+ target[components[components.length - 1]] = object;
+ }
+
+ function forEach_windows(callback) {
+ // Iterate of the the windows [self ... top, opener]. The callback is passed
+ // two objects, the first one is the windows object itself, the second one
+ // is a boolean indicating whether or not its on the same origin as the
+ // current window.
+ var cache = forEach_windows.result_cache;
+ if (!cache) {
+ cache = [[self, true]];
+ var w = self;
+ var i = 0;
+ var so;
+ var origins = location.ancestorOrigins;
+ while (w != w.parent)
+ {
+ w = w.parent;
+ // In WebKit, calls to parent windows' properties that aren't on the same
+ // origin cause an error message to be displayed in the error console but
+ // don't throw an exception. This is a deviation from the current HTML5
+ // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
+ // The problem with WebKit's behavior is that it pollutes the error console
+ // with error messages that can't be caught.
+ //
+ // This issue can be mitigated by relying on the (for now) proprietary
+ // `location.ancestorOrigins` property which returns an ordered list of
+ // the origins of enclosing windows. See:
+ // https://trac.webkit.org/changeset/113945.
+ if(origins) {
+ so = (location.origin == origins[i]);
+ }
+ else
+ {
+ so = is_same_origin(w);
+ }
+ cache.push([w, so]);
+ i++;
+ }
+ w = window.opener;
+ if(w)
+ {
+ // window.opener isn't included in the `location.ancestorOrigins` prop.
+ // We'll just have to deal with a simple check and an error msg on WebKit
+ // browsers in this case.
+ cache.push([w, is_same_origin(w)]);
+ }
+ forEach_windows.result_cache = cache;
+ }
+
+ forEach(cache,
+ function(a)
+ {
+ callback.apply(null, a);
+ });
+ }
+
+ function is_same_origin(w) {
+ try {
+ 'random_prop' in w;
+ return true;
+ } catch(e) {
+ return false;
+ }
+ }
+
+ function supports_post_message(w)
+ {
+ var supports;
+ var type;
+ // Given IE implements postMessage across nested iframes but not across
+ // windows or tabs, you can't infer cross-origin communication from the presence
+ // of postMessage on the current window object only.
+ //
+ // Touching the postMessage prop on a window can throw if the window is
+ // not from the same origin AND post message is not supported in that
+ // browser. So just doing an existence test here won't do, you also need
+ // to wrap it in a try..cacth block.
+ try
+ {
+ type = typeof w.postMessage;
+ if (type === "function")
+ {
+ supports = true;
+ }
+ // IE8 supports postMessage, but implements it as a host object which
+ // returns "object" as its `typeof`.
+ else if (type === "object")
+ {
+ supports = true;
+ }
+ // This is the case where postMessage isn't supported AND accessing a
+ // window property across origins does NOT throw (e.g. old Safari browser).
+ else
+ {
+ supports = false;
+ }
+ }
+ catch(e) {
+ // This is the case where postMessage isn't supported AND accessing a
+ // window property across origins throws (e.g. old Firefox browser).
+ supports = false;
+ }
+ return supports;
+ }
+})();
+// vim: set expandtab shiftwidth=4 tabstop=4:
diff --git a/test/testharnessreport.js b/test/testharnessreport.js
new file mode 100644
index 0000000..fa73347
--- /dev/null
+++ b/test/testharnessreport.js
@@ -0,0 +1,380 @@
+/*
+ * This file is intended for vendors to implement
+ * code needed to integrate testharness.js tests with their own test systems.
+ *
+ * The default implementation extracts metadata from the tests and validates
+ * it against the cached version that should be present in the test source
+ * file. If the cache is not found or is out of sync, source code suitable for
+ * caching the metadata is optionally generated.
+ *
+ * The cached metadata is present for extraction by test processing tools that
+ * are unable to execute javascript.
+ *
+ * Metadata is attached to tests via the properties parameter in the test
+ * constructor. See testharness.js for details.
+ *
+ * Typically test system integration will attach callbacks when each test has
+ * run, using add_result_callback(callback(test)), or when the whole test file
+ * has completed, using
+ * add_completion_callback(callback(tests, harness_status)).
+ *
+ * For more documentation about the callback functions and the
+ * parameters they are called with see testharness.js
+ */
+
+
+
+var metadata_generator = {
+
+ currentMetadata: {},
+ cachedMetadata: false,
+ metadataProperties: ['help', 'assert', 'author'],
+
+ error: function(message) {
+ var messageElement = document.createElement('p');
+ messageElement.setAttribute('class', 'error');
+ this.appendText(messageElement, message);
+
+ var summary = document.getElementById('summary');
+ if (summary) {
+ summary.parentNode.insertBefore(messageElement, summary);
+ }
+ else {
+ document.body.appendChild(messageElement);
+ }
+ },
+
+ /**
+ * Ensure property value has contact information
+ */
+ validateContact: function(test, propertyName) {
+ var result = true;
+ var value = test.properties[propertyName];
+ var values = Array.isArray(value) ? value : [value];
+ for (var index = 0; index < values.length; index++) {
+ value = values[index];
+ var re = /(\S+)(\s*)<(.*)>(.*)/;
+ if (! re.test(value)) {
+ re = /(\S+)(\s+)(http[s]?:\/\/)(.*)/
+ if (! re.test(value)) {
+ this.error('Metadata property "' + propertyName +
+ '" for test: "' + test.name +
+ '" must have name and contact information ' +
+ '("name <email>" or "name http(s)://")');
+ result = false;
+ }
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Extract metadata from test object
+ */
+ extractFromTest: function(test) {
+ var testMetadata = {};
+ // filter out metadata from other properties in test
+ for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
+ metaIndex++) {
+ var meta = this.metadataProperties[metaIndex];
+ if (test.properties.hasOwnProperty(meta)) {
+ if ('author' == meta) {
+ this.validateContact(test, meta);
+ }
+ testMetadata[meta] = test.properties[meta];
+ }
+ }
+ return testMetadata;
+ },
+
+ /**
+ * Compare cached metadata to extracted metadata
+ */
+ validateCache: function() {
+ for (var testName in this.currentMetadata) {
+ if (! this.cachedMetadata.hasOwnProperty(testName)) {
+ return false;
+ }
+ var testMetadata = this.currentMetadata[testName];
+ var cachedTestMetadata = this.cachedMetadata[testName];
+ delete this.cachedMetadata[testName];
+
+ for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
+ metaIndex++) {
+ var meta = this.metadataProperties[metaIndex];
+ if (cachedTestMetadata.hasOwnProperty(meta) &&
+ testMetadata.hasOwnProperty(meta)) {
+ if (Array.isArray(cachedTestMetadata[meta])) {
+ if (! Array.isArray(testMetadata[meta])) {
+ return false;
+ }
+ if (cachedTestMetadata[meta].length ==
+ testMetadata[meta].length) {
+ for (var index = 0;
+ index < cachedTestMetadata[meta].length;
+ index++) {
+ if (cachedTestMetadata[meta][index] !=
+ testMetadata[meta][index]) {
+ return false;
+ }
+ }
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ if (Array.isArray(testMetadata[meta])) {
+ return false;
+ }
+ if (cachedTestMetadata[meta] != testMetadata[meta]) {
+ return false;
+ }
+ }
+ }
+ else if (cachedTestMetadata.hasOwnProperty(meta) ||
+ testMetadata.hasOwnProperty(meta)) {
+ return false;
+ }
+ }
+ }
+ for (var testName in this.cachedMetadata) {
+ return false;
+ }
+ return true;
+ },
+
+ appendText: function(elemement, text) {
+ elemement.appendChild(document.createTextNode(text));
+ },
+
+ jsonifyArray: function(arrayValue, indent) {
+ var output = '[';
+
+ if (1 == arrayValue.length) {
+ output += JSON.stringify(arrayValue[0]);
+ }
+ else {
+ for (var index = 0; index < arrayValue.length; index++) {
+ if (0 < index) {
+ output += ',\n ' + indent;
+ }
+ output += JSON.stringify(arrayValue[index]);
+ }
+ }
+ output += ']';
+ return output;
+ },
+
+ jsonifyObject: function(objectValue, indent) {
+ var output = '{';
+
+ var count = 0;
+ for (var property in objectValue) {
+ ++count;
+ if (Array.isArray(objectValue[property]) ||
+ ('object' == typeof(value))) {
+ ++count;
+ }
+ }
+ if (1 == count) {
+ for (var property in objectValue) {
+ output += ' "' + property + '": '
+ + JSON.stringify(objectValue[property])
+ + ' ';
+ }
+ }
+ else {
+ var first = true;
+ for (var property in objectValue) {
+ if (! first) {
+ output += ',';
+ }
+ first = false;
+ output += '\n ' + indent + '"' + property + '": ';
+ var value = objectValue[property];
+ if (Array.isArray(value)) {
+ output += this.jsonifyArray(value, indent +
+ ' '.substr(0, 5 + property.length));
+ }
+ else if ('object' == typeof(value)) {
+ output += this.jsonifyObject(value, indent + ' ');
+ }
+ else {
+ output += JSON.stringify(value);
+ }
+ }
+ if (1 < output.length) {
+ output += '\n' + indent;
+ }
+ }
+ output += '}';
+ return output;
+ },
+
+ /**
+ * Generate javascript source code for captured metadata
+ * Metadata is in pretty-printed JSON format
+ */
+ generateSource: function() {
+ var source =
+ '<script id="metadata_cache">/*\n' +
+ this.jsonifyObject(this.currentMetadata, '') + '\n' +
+ '*/</script>\n';
+ return source;
+ },
+
+ /**
+ * Add element containing metadata source code
+ */
+ addSourceElement: function(event) {
+ var sourceWrapper = document.createElement('div');
+ sourceWrapper.setAttribute('id', 'metadata_source');
+
+ var instructions = document.createElement('p');
+ if (this.cachedMetadata) {
+ this.appendText(instructions,
+ 'Replace the existing <script id="metadata_cache"> element ' +
+ 'in the test\'s <head> with the following:');
+ }
+ else {
+ this.appendText(instructions,
+ 'Copy the following into the <head> element of the test ' +
+ 'or the test\'s metadata sidecar file:');
+ }
+ sourceWrapper.appendChild(instructions);
+
+ var sourceElement = document.createElement('pre');
+ this.appendText(sourceElement, this.generateSource());
+
+ sourceWrapper.appendChild(sourceElement);
+
+ var messageElement = document.getElementById('metadata_issue');
+ messageElement.parentNode.insertBefore(sourceWrapper,
+ messageElement.nextSibling);
+ messageElement.parentNode.removeChild(messageElement);
+
+ (event.preventDefault) ? event.preventDefault() :
+ event.returnValue = false;
+ },
+
+ /**
+ * Extract the metadata cache from the cache element if present
+ */
+ getCachedMetadata: function() {
+ var cacheElement = document.getElementById('metadata_cache');
+
+ if (cacheElement) {
+ var cacheText = cacheElement.firstChild.nodeValue;
+ var openBrace = cacheText.indexOf('{');
+ var closeBrace = cacheText.lastIndexOf('}');
+ if ((-1 < openBrace) && (-1 < closeBrace)) {
+ cacheText = cacheText.slice(openBrace, closeBrace + 1);
+ try {
+ this.cachedMetadata = JSON.parse(cacheText);
+ }
+ catch (exc) {
+ this.cachedMetadata = 'Invalid JSON in Cached metadata. ';
+ }
+ }
+ else {
+ this.cachedMetadata = 'Metadata not found in cache element. ';
+ }
+ }
+ },
+
+ /**
+ * Main entry point, extract metadata from tests, compare to cached version
+ * if present.
+ * If cache not present or differs from extrated metadata, generate an error
+ */
+ process: function(tests, harness_status) {
+ for (var index = 0; index < tests.length; index++) {
+ var test = tests[index];
+ if (this.currentMetadata.hasOwnProperty(test.name)) {
+ this.error('Duplicate test name: ' + test.name);
+ }
+ else {
+ this.currentMetadata[test.name] = this.extractFromTest(test);
+ }
+ }
+
+ this.getCachedMetadata();
+
+ var message = null;
+ var messageClass = 'warning';
+ var showSource = false;
+
+ if (0 == tests.length) {
+ if (this.cachedMetadata) {
+ message = 'Cached metadata present but no tests. ';
+ }
+ }
+ else if (1 == tests.length) {
+ if (this.cachedMetadata) {
+ message = 'Single test files should not have cached metadata. ';
+ }
+ else {
+ var testMetadata = this.currentMetadata[tests[0].name];
+ var hasMetadata = false;
+ for (var meta in testMetadata) {
+ hasMetadata |= testMetadata.hasOwnProperty(meta);
+ }
+ if (hasMetadata) {
+ message = 'Single tests should not have metadata. ' +
+ 'Move metadata to <head>. ';
+ }
+ }
+ }
+ else {
+ if (this.cachedMetadata) {
+ messageClass = 'error';
+ if ('string' == typeof(this.cachedMetadata)) {
+ message = this.cachedMetadata;
+ showSource = true;
+ }
+ else if (! this.validateCache()) {
+ message = 'Cached metadata out of sync. ';
+ showSource = true;
+ }
+ }
+ }
+
+ if (message) {
+ var messageElement = document.createElement('p');
+ messageElement.setAttribute('id', 'metadata_issue');
+ messageElement.setAttribute('class', messageClass);
+ this.appendText(messageElement, message);
+
+ if (showSource) {
+ var link = document.createElement('a');
+ this.appendText(link, 'Click for source code.');
+ link.setAttribute('href', '#');
+ link.setAttribute('onclick',
+ 'metadata_generator.addSourceElement(event)');
+ messageElement.appendChild(link);
+ }
+
+ var summary = document.getElementById('summary');
+ if (summary) {
+ summary.parentNode.insertBefore(messageElement, summary);
+ }
+ else {
+ var log = document.getElementById('log');
+ if (log) {
+ log.appendChild(messageElement);
+ }
+ }
+ }
+ },
+
+ setup: function() {
+ add_completion_callback(
+ function (tests, harness_status) {
+ metadata_generator.process(tests, harness_status)
+ });
+ }
+}
+
+metadata_generator.setup();
+// vim: set expandtab shiftwidth=4 tabstop=4:
diff --git a/test/tests.html b/test/tests.html
new file mode 100644
index 0000000..526351c
--- /dev/null
+++ b/test/tests.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Encoding API Tests</title>
+<link rel="stylesheet" href="testharness.css">
+</head>
+<body>
+<h1>Encoding API Tests</h1>
+<div id="log"></div>
+<script src="testharness.js"></script>
+<script src="testharnessreport.js"></script>
+<script>
+setup({explicit_timeout: true});
+</script>
+
+<script src="../lib/encoding-indexes.js"></script>
+<script src="../lib/encoding.js"></script>
+<script src="test-misc.js"></script>
+<script src="test-utf.js"></script>
+<!-- TODO: test for all single-byte encoding indexes -->
+
+<!-- TODO: gb18030 -->
+
+<script src="test-big5.js"></script>
+<script src="test-euc-jp.js"></script>
+<script src="test-iso-2022-jp.js"></script>
+<script src="test-shift_jis.js"></script>
+<script src="test-euc-kr.js"></script>
+<script src="test-x-user-defined.js"></script>
+</body>
+</html>
diff --git a/util/externs.js b/util/externs.js
new file mode 100644
index 0000000..aea77d4
--- /dev/null
+++ b/util/externs.js
@@ -0,0 +1,28 @@
+//
+// Externs for Closure Compiler
+// https://developers.google.com/closure/compiler/
+//
+// Usage:
+// java -jar compiler.jar \
+// --jscomp_warning reportUnknownTypes \
+// --warning_level VERBOSE \
+// --summary_detail_level 3 \
+// --externs util/externs.js \
+// lib/encoding.js
+//
+
+/**
+ * @param {string} name
+ * @return {*}
+ */
+function require(name) {}
+
+/**
+ * @type {Object}
+ */
+var module;
+
+/**
+ * @type {Object.<string,*>}
+ */
+module.exports;
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-text-encoding.git
More information about the Pkg-javascript-commits
mailing list