From d55e4545829b1386750bdbf4184c91168f41e565 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Fri, 3 Feb 2017 23:12:53 -0500 Subject: [PATCH 01/19] Fix typos in imports and i18n `app/ui.js` had an incorrect import path which caused issues when using the ES6 and/or CommonJS builds of noVNC. `core/util.js` had a non-strict-compatible declaration of a variable without a `let` or `var` (it now uses `let`). This fixes both issues. --- app/ui.js | 2 +- core/util.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ui.js b/app/ui.js index 33cd1d48..56bd9f74 100644 --- a/app/ui.js +++ b/app/ui.js @@ -14,7 +14,7 @@ /* [module] * import Util from "../core/util"; * import KeyTable from "../core/input/keysym"; - * import keysyms from "./keysymdef"; + * import keysyms from "../core/input/keysymdef"; * import RFB from "../core/rfb"; * import Display from "../core/display"; * import WebUtil from "./webutil"; diff --git a/core/util.js b/core/util.js index 8edaace7..550ef916 100644 --- a/core/util.js +++ b/core/util.js @@ -466,7 +466,7 @@ Util.Localisation = { } for (var i = 0;i < elem.childNodes.length;i++) { - node = elem.childNodes[i]; + let node = elem.childNodes[i]; if (node.nodeType === node.ELEMENT_NODE) { process(node, enabled); } else if (node.nodeType === node.TEXT_NODE && enabled) { From 3ae0bb0968907dad1f730372f5309dc2cf0ea000 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Fri, 3 Feb 2017 23:17:44 -0500 Subject: [PATCH 02/19] Uncomment ES6 module syntax This removes the special comment part of the ES6 module syntax, opting to enable ES6 module syntax by default. It also appends `.js` to all import paths to better support in-browser loading. --- app/ui.js | 28 ++++++++-------------------- app/webutil.js | 6 ++---- core/base64.js | 2 +- core/des.js | 2 +- core/display.js | 9 ++++----- core/input/devices.js | 11 +++++------ core/input/keysym.js | 2 +- core/input/keysymdef.js | 2 +- core/input/util.js | 9 ++++----- core/input/xtscancodes.js | 2 +- core/rfb.js | 23 +++++++++++------------ core/util.js | 2 +- core/websock.js | 7 +++---- 13 files changed, 43 insertions(+), 62 deletions(-) diff --git a/app/ui.js b/app/ui.js index 56bd9f74..8009c382 100644 --- a/app/ui.js +++ b/app/ui.js @@ -11,14 +11,12 @@ /* jslint white: false, browser: true */ /* global window, document.getElementById, Util, WebUtil, RFB, Display */ -/* [module] - * import Util from "../core/util"; - * import KeyTable from "../core/input/keysym"; - * import keysyms from "../core/input/keysymdef"; - * import RFB from "../core/rfb"; - * import Display from "../core/display"; - * import WebUtil from "./webutil"; - */ +import Util from "../core/util.js"; +import KeyTable from "../core/input/keysym.js"; +import keysyms from "../core/input/keysymdef.js"; +import RFB from "../core/rfb.js"; +import Display from "../core/display.js"; +import WebUtil from "./webutil.js"; var UI; @@ -73,16 +71,6 @@ var UI; {'app': ["locale/" + Util.Localisation.language + ".js"]}); } - /* [begin skip-as-module] */ - // Load supporting scripts - WebUtil.load_scripts( - {'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js", - "input/xtscancodes.js", "input/util.js", "input/devices.js", - "display.js", "inflator.js", "rfb.js", "input/keysym.js"]}); - - window.onscriptsload = function () { UI.load(); }; - /* [end skip-as-module] */ - var _ = Util.Localisation.get; UI = { @@ -1755,7 +1743,7 @@ var UI; */ }; - /* [module] UI.load(); */ + UI.load(); })(); -/* [module] export default UI; */ +export default UI; diff --git a/app/webutil.js b/app/webutil.js index e6e6afb7..6afd015f 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -10,9 +10,7 @@ /*jslint bitwise: false, white: false, browser: true, devel: true */ /*global Util, window, document */ -/* [module] - * import Util from "../core/util"; - */ +import Util from "../core/util.js"; // Globals defined here var WebUtil = {}; @@ -308,4 +306,4 @@ WebUtil.load_scripts = function (files_by_dir) { } }; -/* [module] export default WebUtil; */ +export default WebUtil; diff --git a/core/base64.js b/core/base64.js index 2b4f948f..3b9ebe54 100644 --- a/core/base64.js +++ b/core/base64.js @@ -112,4 +112,4 @@ var Base64 = { } }; /* End of Base64 namespace */ -/* [module] export default Base64; */ +export default Base64; diff --git a/core/des.js b/core/des.js index c9a4753a..62684936 100644 --- a/core/des.js +++ b/core/des.js @@ -77,7 +77,7 @@ /* jslint white: false */ -/* [module] export default */ function DES(passwd) { +export default function DES(passwd) { "use strict"; // Tables, permutations, S-boxes, etc. diff --git a/core/display.js b/core/display.js index f7c437b4..a1dac7a4 100644 --- a/core/display.js +++ b/core/display.js @@ -10,12 +10,11 @@ /*jslint browser: true, white: false */ /*global Util, Base64, changeCursor */ -/* [module] - * import Util from "./util"; - * import Base64 from "./base64"; - */ +import Util from "./util.js"; +import Base64 from "./base64.js"; -/* [module] export default */ function Display(defaults) { + +export default function Display(defaults) { this._drawCtx = null; this._c_forceCanvas = false; diff --git a/core/input/devices.js b/core/input/devices.js index 2e41122e..74317ca8 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -8,12 +8,11 @@ /*jslint browser: true, white: false */ /*global window, Util */ -/* [module] - * import Util from "../util"; - * import KeyboardUtil from "./util"; - */ +import Util from "../util.js"; +import KeyboardUtil from "./util.js"; -/* [module] export */ var Keyboard; + +export var Keyboard; (function () { "use strict"; @@ -148,7 +147,7 @@ ]); })(); -/* [module] export */ var Mouse; +export var Mouse; (function () { Mouse = function (defaults) { diff --git a/core/input/keysym.js b/core/input/keysym.js index 5983c38a..15cc1bc7 100644 --- a/core/input/keysym.js +++ b/core/input/keysym.js @@ -379,4 +379,4 @@ var KeyTable = { XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ }; -/* [module] export default KeyTable; */ +export default KeyTable; diff --git a/core/input/keysymdef.js b/core/input/keysymdef.js index c4d0ace9..e618c167 100644 --- a/core/input/keysymdef.js +++ b/core/input/keysymdef.js @@ -21,4 +21,4 @@ var keysyms = (function(){ }; })(); -/* [module] export default keysyms */ +export default keysyms diff --git a/core/input/util.js b/core/input/util.js index 52b31283..f34a9abf 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -1,7 +1,6 @@ -/* [module] - * import KeyTable from "./keysym"; - * import keysyms from "./keysymdef"; - */ +import KeyTable from "./keysym.js"; +import keysyms from "./keysymdef.js"; + var KeyboardUtil = {}; @@ -676,4 +675,4 @@ KeyboardUtil.EscapeModifiers = function(next) { }; }; -/* [module] export default KeyboardUtil; */ +export default KeyboardUtil; diff --git a/core/input/xtscancodes.js b/core/input/xtscancodes.js index 542bcf73..611a80be 100644 --- a/core/input/xtscancodes.js +++ b/core/input/xtscancodes.js @@ -148,4 +148,4 @@ var XtScancode = { "MediaSelect": 0xE06D, }; -/* [module] export default XtScancode */ +export default XtScancode diff --git a/core/rfb.js b/core/rfb.js index baf5f3ce..caf96cfd 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -10,21 +10,20 @@ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) */ -/* [module] - * import Util from "./util"; - * import Display from "./display"; - * import { Keyboard, Mouse } from "./input/devices" - * import Websock from "./websock" - * import Base64 from "./base64"; - * import DES from "./des"; - * import KeyTable from "./input/keysym"; - * import XtScancode from "./input/xtscancodes"; - * import Inflator from "./inflator.mod"; - */ +import Util from "./util.js"; +import Display from "./display.js"; +import { Keyboard, Mouse } from "./input/devices.js"; +import Websock from "./websock.js"; +import Base64 from "./base64.js"; +import DES from "./des.js"; +import KeyTable from "./input/keysym.js"; +import XtScancode from "./input/xtscancodes.js"; +import Inflator from "./inflator.mod.js"; + /*jslint white: false, browser: true */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */ -/* [module] export default */ function RFB(defaults) { +export default function RFB(defaults) { "use strict"; if (!defaults) { defaults = {}; diff --git a/core/util.js b/core/util.js index 550ef916..9f048914 100644 --- a/core/util.js +++ b/core/util.js @@ -618,4 +618,4 @@ Util.releaseCapture = function () { } }; -/* [module] export default Util; */ +export default Util; diff --git a/core/websock.js b/core/websock.js index 51d9b625..7cb04667 100644 --- a/core/websock.js +++ b/core/websock.js @@ -12,14 +12,13 @@ * read binary data off of the receive queue. */ -/* [module] - * import Util from "./util"; - */ +import Util from "./util.js"; + /*jslint browser: true, bitwise: true */ /*global Util*/ -/* [module] export default */ function Websock() { +export default function Websock() { "use strict"; this._websocket = null; // WebSocket object From fba220c6fca6786c77c379259df91ded9878be5c Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Fri, 3 Feb 2017 23:55:00 -0500 Subject: [PATCH 03/19] Vendor an ES6-modules-compatible Pako This commit vendors a converted, stripped-down version of Pako which uses ES6 modules instead of CommonJS modules. --- .gitignore | 6 +- LICENSE.txt | 6 +- core/inflator.js | 2434 +---------------------------- core/inflator.mod.js | 40 - core/rfb.js | 4 +- package.json | 3 - vendor/pako/LICENSE | 21 + vendor/pako/README.md | 6 + vendor/pako/lib/utils/common.js | 45 + vendor/pako/lib/zlib/adler32.js | 27 + vendor/pako/lib/zlib/constants.js | 47 + vendor/pako/lib/zlib/crc32.js | 36 + vendor/pako/lib/zlib/deflate.js | 1846 ++++++++++++++++++++++ vendor/pako/lib/zlib/gzheader.js | 35 + vendor/pako/lib/zlib/inffast.js | 324 ++++ vendor/pako/lib/zlib/inflate.js | 1527 ++++++++++++++++++ vendor/pako/lib/zlib/inftrees.js | 322 ++++ vendor/pako/lib/zlib/messages.js | 11 + vendor/pako/lib/zlib/trees.js | 1195 ++++++++++++++ vendor/pako/lib/zlib/zstream.js | 24 + 20 files changed, 5484 insertions(+), 2475 deletions(-) delete mode 100644 core/inflator.mod.js create mode 100644 vendor/pako/LICENSE create mode 100644 vendor/pako/README.md create mode 100644 vendor/pako/lib/utils/common.js create mode 100644 vendor/pako/lib/zlib/adler32.js create mode 100644 vendor/pako/lib/zlib/constants.js create mode 100644 vendor/pako/lib/zlib/crc32.js create mode 100644 vendor/pako/lib/zlib/deflate.js create mode 100644 vendor/pako/lib/zlib/gzheader.js create mode 100644 vendor/pako/lib/zlib/inffast.js create mode 100644 vendor/pako/lib/zlib/inflate.js create mode 100644 vendor/pako/lib/zlib/inftrees.js create mode 100644 vendor/pako/lib/zlib/messages.js create mode 100644 vendor/pako/lib/zlib/trees.js create mode 100644 vendor/pako/lib/zlib/zstream.js diff --git a/.gitignore b/.gitignore index da1b8c22..921551cc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ tests/data_*.js utils/rebind.so utils/websockify -node_modules -build -lib +/node_modules +/build +/lib recordings diff --git a/LICENSE.txt b/LICENSE.txt index 7b5de592..39cbe3f2 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -18,6 +18,7 @@ is not limited to): core/websock.js app/webutil.js core/input/xtscancodes.js + core/inflator.js The HTML, CSS, font and images files that included with the noVNC source distibution (or repository) are not considered part of the @@ -49,8 +50,7 @@ licenses (all MPL 2.0 compatible): core/des.js : Various BSD style licenses - utils/inflator.mod.js - include/inflator.js : MIT (for pako) + vendor/pako/ : MIT Any other files not mentioned above are typically marked with a copyright/license header at the top of the file. The default noVNC @@ -66,7 +66,7 @@ The following license texts are included: docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD) docs/LICENSE.zlib docs/LICENSE.Apache-2.0 - docs/LICENSE.pako + vendor/pako/LICENSE (MIT) Or alternatively the license texts may be found here: diff --git a/core/inflator.js b/core/inflator.js index 973836d7..68fd8298 100644 --- a/core/inflator.js +++ b/core/inflator.js @@ -1,15 +1,5 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Inflator = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o>> 16) & 0xffff) |0, - n = 0; - - while (len !== 0) { - // Set limit ~ twice less than 5552, to keep - // s2 in 31-bits, because we force signed ints. - // in other case %= will fail. - n = len > 2000 ? 2000 : len; - len -= n; - - do { - s1 = (s1 + buf[pos++]) |0; - s2 = (s2 + s1) |0; - } while (--n); - - s1 %= 65521; - s2 %= 65521; - } - - return (s1 | (s2 << 16)) |0; -} - - -module.exports = adler32; - -},{}],4:[function(require,module,exports){ -'use strict'; - -// Note: we can't get significant speed boost here. -// So write code to minimize size - no pregenerated tables -// and array tools dependencies. - - -// Use ordinary array, since untyped makes no boost here -function makeTable() { - var c, table = []; - - for (var n = 0; n < 256; n++) { - c = n; - for (var k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - table[n] = c; - } - - return table; -} - -// Create table on load. Just 255 signed longs. Not a problem. -var crcTable = makeTable(); - - -function crc32(crc, buf, len, pos) { - var t = crcTable, - end = pos + len; - - crc ^= -1; - - for (var i = pos; i < end; i++) { - crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; - } - - return (crc ^ (-1)); // >>> 0; -} - - -module.exports = crc32; - -},{}],5:[function(require,module,exports){ -'use strict'; - -// See state defs from inflate.js -var BAD = 30; /* got a data error -- remain here until reset */ -var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ - -/* - Decode literal, length, and distance codes and write out the resulting - literal and match bytes until either not enough input or output is - available, an end-of-block is encountered, or a data error is encountered. - When large enough input and output buffers are supplied to inflate(), for - example, a 16K input buffer and a 64K output buffer, more than 95% of the - inflate execution time is spent in this routine. - - Entry assumptions: - - state.mode === LEN - strm.avail_in >= 6 - strm.avail_out >= 258 - start >= strm.avail_out - state.bits < 8 - - On return, state.mode is one of: - - LEN -- ran out of enough output space or enough available input - TYPE -- reached end of block code, inflate() to interpret next block - BAD -- error in block data - - Notes: - - - The maximum input bits used by a length/distance pair is 15 bits for the - length code, 5 bits for the length extra, 15 bits for the distance code, - and 13 bits for the distance extra. This totals 48 bits, or six bytes. - Therefore if strm.avail_in >= 6, then there is enough input to avoid - checking for available input while decoding. - - - The maximum bytes that a single length/distance pair can output is 258 - bytes, which is the maximum length that can be coded. inflate_fast() - requires strm.avail_out >= 258 for each loop to avoid checking for - output space. - */ -module.exports = function inflate_fast(strm, start) { - var state; - var _in; /* local strm.input */ - var last; /* have enough input while in < last */ - var _out; /* local strm.output */ - var beg; /* inflate()'s initial strm.output */ - var end; /* while out < end, enough space available */ -//#ifdef INFLATE_STRICT - var dmax; /* maximum distance from zlib header */ -//#endif - var wsize; /* window size or zero if not using window */ - var whave; /* valid bytes in the window */ - var wnext; /* window write index */ - // Use `s_window` instead `window`, avoid conflict with instrumentation tools - var s_window; /* allocated sliding window, if wsize != 0 */ - var hold; /* local strm.hold */ - var bits; /* local strm.bits */ - var lcode; /* local strm.lencode */ - var dcode; /* local strm.distcode */ - var lmask; /* mask for first level of length codes */ - var dmask; /* mask for first level of distance codes */ - var here; /* retrieved table entry */ - var op; /* code bits, operation, extra bits, or */ - /* window position, window bytes to copy */ - var len; /* match length, unused bytes */ - var dist; /* match distance */ - var from; /* where to copy match from */ - var from_source; - - - var input, output; // JS specific, because we have no pointers - - /* copy state to local variables */ - state = strm.state; - //here = state.here; - _in = strm.next_in; - input = strm.input; - last = _in + (strm.avail_in - 5); - _out = strm.next_out; - output = strm.output; - beg = _out - (start - strm.avail_out); - end = _out + (strm.avail_out - 257); -//#ifdef INFLATE_STRICT - dmax = state.dmax; -//#endif - wsize = state.wsize; - whave = state.whave; - wnext = state.wnext; - s_window = state.window; - hold = state.hold; - bits = state.bits; - lcode = state.lencode; - dcode = state.distcode; - lmask = (1 << state.lenbits) - 1; - dmask = (1 << state.distbits) - 1; - - - /* decode literals and length/distances until end-of-block or not enough - input data or output space */ - - top: - do { - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - - here = lcode[hold & lmask]; - - dolen: - for (;;) { // Goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - if (op === 0) { /* literal */ - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - output[_out++] = here & 0xffff/*here.val*/; - } - else if (op & 16) { /* length base */ - len = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (op) { - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - len += hold & ((1 << op) - 1); - hold >>>= op; - bits -= op; - } - //Tracevv((stderr, "inflate: length %u\n", len)); - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - here = dcode[hold & dmask]; - - dodist: - for (;;) { // goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - - if (op & 16) { /* distance base */ - dist = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - } - dist += hold & ((1 << op) - 1); -//#ifdef INFLATE_STRICT - if (dist > dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break top; - } -//#endif - hold >>>= op; - bits -= op; - //Tracevv((stderr, "inflate: distance %u\n", dist)); - op = _out - beg; /* max distance in output */ - if (dist > op) { /* see if copy from window */ - op = dist - op; /* distance back in window */ - if (op > whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break top; - } - -// (!) This block is disabled in zlib defailts, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// if (len <= op - whave) { -// do { -// output[_out++] = 0; -// } while (--len); -// continue top; -// } -// len -= op - whave; -// do { -// output[_out++] = 0; -// } while (--op > whave); -// if (op === 0) { -// from = _out - dist; -// do { -// output[_out++] = output[from++]; -// } while (--len); -// continue top; -// } -//#endif - } - from = 0; // window index - from_source = s_window; - if (wnext === 0) { /* very common case */ - from += wsize - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - else if (wnext < op) { /* wrap around window */ - from += wsize + wnext - op; - op -= wnext; - if (op < len) { /* some from end of window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = 0; - if (wnext < len) { /* some from start of window */ - op = wnext; - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - } - else { /* contiguous in window */ - from += wnext - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - while (len > 2) { - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - len -= 3; - } - if (len) { - output[_out++] = from_source[from++]; - if (len > 1) { - output[_out++] = from_source[from++]; - } - } - } - else { - from = _out - dist; /* copy direct from output */ - do { /* minimum length is three */ - output[_out++] = output[from++]; - output[_out++] = output[from++]; - output[_out++] = output[from++]; - len -= 3; - } while (len > 2); - if (len) { - output[_out++] = output[from++]; - if (len > 1) { - output[_out++] = output[from++]; - } - } - } - } - else if ((op & 64) === 0) { /* 2nd level distance code */ - here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dodist; - } - else { - strm.msg = 'invalid distance code'; - state.mode = BAD; - break top; - } - - break; // need to emulate goto via "continue" - } - } - else if ((op & 64) === 0) { /* 2nd level length code */ - here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dolen; - } - else if (op & 32) { /* end-of-block */ - //Tracevv((stderr, "inflate: end of block\n")); - state.mode = TYPE; - break top; - } - else { - strm.msg = 'invalid literal/length code'; - state.mode = BAD; - break top; - } - - break; // need to emulate goto via "continue" - } - } while (_in < last && _out < end); - - /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ - len = bits >> 3; - _in -= len; - bits -= len << 3; - hold &= (1 << bits) - 1; - - /* update state and return */ - strm.next_in = _in; - strm.next_out = _out; - strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); - strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); - state.hold = hold; - state.bits = bits; - return; -}; - -},{}],6:[function(require,module,exports){ -'use strict'; - - -var utils = require('../utils/common'); -var adler32 = require('./adler32'); -var crc32 = require('./crc32'); -var inflate_fast = require('./inffast'); -var inflate_table = require('./inftrees'); - -var CODES = 0; -var LENS = 1; -var DISTS = 2; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - - -/* Allowed flush values; see deflate() and inflate() below for details */ -//var Z_NO_FLUSH = 0; -//var Z_PARTIAL_FLUSH = 1; -//var Z_SYNC_FLUSH = 2; -//var Z_FULL_FLUSH = 3; -var Z_FINISH = 4; -var Z_BLOCK = 5; -var Z_TREES = 6; - - -/* Return codes for the compression/decompression functions. Negative values - * are errors, positive values are used for special but normal events. - */ -var Z_OK = 0; -var Z_STREAM_END = 1; -var Z_NEED_DICT = 2; -//var Z_ERRNO = -1; -var Z_STREAM_ERROR = -2; -var Z_DATA_ERROR = -3; -var Z_MEM_ERROR = -4; -var Z_BUF_ERROR = -5; -//var Z_VERSION_ERROR = -6; - -/* The deflate compression method */ -var Z_DEFLATED = 8; - - -/* STATES ====================================================================*/ -/* ===========================================================================*/ - - -var HEAD = 1; /* i: waiting for magic header */ -var FLAGS = 2; /* i: waiting for method and flags (gzip) */ -var TIME = 3; /* i: waiting for modification time (gzip) */ -var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ -var EXLEN = 5; /* i: waiting for extra length (gzip) */ -var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ -var NAME = 7; /* i: waiting for end of file name (gzip) */ -var COMMENT = 8; /* i: waiting for end of comment (gzip) */ -var HCRC = 9; /* i: waiting for header crc (gzip) */ -var DICTID = 10; /* i: waiting for dictionary check value */ -var DICT = 11; /* waiting for inflateSetDictionary() call */ -var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ -var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ -var STORED = 14; /* i: waiting for stored size (length and complement) */ -var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ -var COPY = 16; /* i/o: waiting for input or output to copy stored block */ -var TABLE = 17; /* i: waiting for dynamic block table lengths */ -var LENLENS = 18; /* i: waiting for code length code lengths */ -var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ -var LEN_ = 20; /* i: same as LEN below, but only first time in */ -var LEN = 21; /* i: waiting for length/lit/eob code */ -var LENEXT = 22; /* i: waiting for length extra bits */ -var DIST = 23; /* i: waiting for distance code */ -var DISTEXT = 24; /* i: waiting for distance extra bits */ -var MATCH = 25; /* o: waiting for output space to copy string */ -var LIT = 26; /* o: waiting for output space to write literal */ -var CHECK = 27; /* i: waiting for 32-bit check value */ -var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ -var DONE = 29; /* finished check, done -- remain here until reset */ -var BAD = 30; /* got a data error -- remain here until reset */ -var MEM = 31; /* got an inflate() memory error -- remain here until reset */ -var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ - -/* ===========================================================================*/ - - - -var ENOUGH_LENS = 852; -var ENOUGH_DISTS = 592; -//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -var MAX_WBITS = 15; -/* 32K LZ77 window */ -var DEF_WBITS = MAX_WBITS; - - -function zswap32(q) { - return (((q >>> 24) & 0xff) + - ((q >>> 8) & 0xff00) + - ((q & 0xff00) << 8) + - ((q & 0xff) << 24)); -} - - -function InflateState() { - this.mode = 0; /* current inflate mode */ - this.last = false; /* true if processing last block */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ - this.havedict = false; /* true if dictionary provided */ - this.flags = 0; /* gzip header method and flags (0 if zlib) */ - this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ - this.check = 0; /* protected copy of check value */ - this.total = 0; /* protected copy of output count */ - // TODO: may be {} - this.head = null; /* where to save gzip header information */ - - /* sliding window */ - this.wbits = 0; /* log base 2 of requested window size */ - this.wsize = 0; /* window size or zero if not using window */ - this.whave = 0; /* valid bytes in the window */ - this.wnext = 0; /* window write index */ - this.window = null; /* allocated sliding window, if needed */ - - /* bit accumulator */ - this.hold = 0; /* input bit accumulator */ - this.bits = 0; /* number of bits in "in" */ - - /* for string and stored block copying */ - this.length = 0; /* literal or length of data to copy */ - this.offset = 0; /* distance back to copy string from */ - - /* for table and code decoding */ - this.extra = 0; /* extra bits needed */ - - /* fixed and dynamic code tables */ - this.lencode = null; /* starting table for length/literal codes */ - this.distcode = null; /* starting table for distance codes */ - this.lenbits = 0; /* index bits for lencode */ - this.distbits = 0; /* index bits for distcode */ - - /* dynamic table building */ - this.ncode = 0; /* number of code length code lengths */ - this.nlen = 0; /* number of length code lengths */ - this.ndist = 0; /* number of distance code lengths */ - this.have = 0; /* number of code lengths in lens[] */ - this.next = null; /* next available space in codes[] */ - - this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ - this.work = new utils.Buf16(288); /* work area for code table building */ - - /* - because we don't have pointers in js, we use lencode and distcode directly - as buffers so we don't need codes - */ - //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ - this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ - this.distdyn = null; /* dynamic table for distance codes (JS specific) */ - this.sane = 0; /* if false, allow invalid distance too far */ - this.back = 0; /* bits back of last unprocessed length/lit */ - this.was = 0; /* initial length of match */ -} - -function inflateResetKeep(strm) { - var state; - - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - strm.total_in = strm.total_out = state.total = 0; - strm.msg = ''; /*Z_NULL*/ - if (state.wrap) { /* to support ill-conceived Java test suite */ - strm.adler = state.wrap & 1; - } - state.mode = HEAD; - state.last = 0; - state.havedict = 0; - state.dmax = 32768; - state.head = null/*Z_NULL*/; - state.hold = 0; - state.bits = 0; - //state.lencode = state.distcode = state.next = state.codes; - state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); - state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); - - state.sane = 1; - state.back = -1; - //Tracev((stderr, "inflate: reset\n")); - return Z_OK; -} - -function inflateReset(strm) { - var state; - - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - state.wsize = 0; - state.whave = 0; - state.wnext = 0; - return inflateResetKeep(strm); - -} - -function inflateReset2(strm, windowBits) { - var wrap; - var state; - - /* get the state */ - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - - /* extract wrap request from windowBits parameter */ - if (windowBits < 0) { - wrap = 0; - windowBits = -windowBits; - } - else { - wrap = (windowBits >> 4) + 1; - if (windowBits < 48) { - windowBits &= 15; - } - } - - /* set number of window bits, free window if different */ - if (windowBits && (windowBits < 8 || windowBits > 15)) { - return Z_STREAM_ERROR; - } - if (state.window !== null && state.wbits !== windowBits) { - state.window = null; - } - - /* update state and reset the rest of it */ - state.wrap = wrap; - state.wbits = windowBits; - return inflateReset(strm); -} - -function inflateInit2(strm, windowBits) { - var ret; - var state; - - if (!strm) { return Z_STREAM_ERROR; } - //strm.msg = Z_NULL; /* in case we return an error */ - - state = new InflateState(); - - //if (state === Z_NULL) return Z_MEM_ERROR; - //Tracev((stderr, "inflate: allocated\n")); - strm.state = state; - state.window = null/*Z_NULL*/; - ret = inflateReset2(strm, windowBits); - if (ret !== Z_OK) { - strm.state = null/*Z_NULL*/; - } - return ret; -} - -function inflateInit(strm) { - return inflateInit2(strm, DEF_WBITS); -} - - -/* - Return state with length and distance decoding tables and index sizes set to - fixed code decoding. Normally this returns fixed tables from inffixed.h. - If BUILDFIXED is defined, then instead this routine builds the tables the - first time it's called, and returns those tables the first time and - thereafter. This reduces the size of the code by about 2K bytes, in - exchange for a little execution time. However, BUILDFIXED should not be - used for threaded applications, since the rewriting of the tables and virgin - may not be thread-safe. - */ -var virgin = true; - -var lenfix, distfix; // We have no pointers in JS, so keep tables separate - -function fixedtables(state) { - /* build fixed huffman tables if first call (may not be thread safe) */ - if (virgin) { - var sym; - - lenfix = new utils.Buf32(512); - distfix = new utils.Buf32(32); - - /* literal/length table */ - sym = 0; - while (sym < 144) { state.lens[sym++] = 8; } - while (sym < 256) { state.lens[sym++] = 9; } - while (sym < 280) { state.lens[sym++] = 7; } - while (sym < 288) { state.lens[sym++] = 8; } - - inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); - - /* distance table */ - sym = 0; - while (sym < 32) { state.lens[sym++] = 5; } - - inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); - - /* do this just once */ - virgin = false; - } - - state.lencode = lenfix; - state.lenbits = 9; - state.distcode = distfix; - state.distbits = 5; -} - - -/* - Update the window with the last wsize (normally 32K) bytes written before - returning. If window does not exist yet, create it. This is only called - when a window is already in use, or when output has been written during this - inflate call, but the end of the deflate stream has not been reached yet. - It is also called to create a window for dictionary data when a dictionary - is loaded. - - Providing output buffers larger than 32K to inflate() should provide a speed - advantage, since only the last 32K of output is copied to the sliding window - upon return from inflate(), and since all distances after the first 32K of - output will fall in the output data, making match copies simpler and faster. - The advantage may be dependent on the size of the processor's data caches. - */ -function updatewindow(strm, src, end, copy) { - var dist; - var state = strm.state; - - /* if it hasn't been done already, allocate space for the window */ - if (state.window === null) { - state.wsize = 1 << state.wbits; - state.wnext = 0; - state.whave = 0; - - state.window = new utils.Buf8(state.wsize); - } - - /* copy state->wsize or less output bytes into the circular window */ - if (copy >= state.wsize) { - utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0); - state.wnext = 0; - state.whave = state.wsize; - } - else { - dist = state.wsize - state.wnext; - if (dist > copy) { - dist = copy; - } - //zmemcpy(state->window + state->wnext, end - copy, dist); - utils.arraySet(state.window, src, end - copy, dist, state.wnext); - copy -= dist; - if (copy) { - //zmemcpy(state->window, end - copy, copy); - utils.arraySet(state.window, src, end - copy, copy, 0); - state.wnext = copy; - state.whave = state.wsize; - } - else { - state.wnext += dist; - if (state.wnext === state.wsize) { state.wnext = 0; } - if (state.whave < state.wsize) { state.whave += dist; } - } - } - return 0; -} - -function inflate(strm, flush) { - var state; - var input, output; // input/output buffers - var next; /* next input INDEX */ - var put; /* next output INDEX */ - var have, left; /* available input and output */ - var hold; /* bit buffer */ - var bits; /* bits in bit buffer */ - var _in, _out; /* save starting available input and output */ - var copy; /* number of stored or match bytes to copy */ - var from; /* where to copy match bytes from */ - var from_source; - var here = 0; /* current decoding table entry */ - var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) - //var last; /* parent table entry */ - var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) - var len; /* length to copy for repeats, bits to drop */ - var ret; /* return code */ - var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ - var opts; - - var n; // temporary var for NEED_BITS - - var order = /* permutation of code lengths */ - [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; - - - if (!strm || !strm.state || !strm.output || - (!strm.input && strm.avail_in !== 0)) { - return Z_STREAM_ERROR; - } - - state = strm.state; - if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ - - - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - _in = have; - _out = left; - ret = Z_OK; - - inf_leave: // goto emulation - for (;;) { - switch (state.mode) { - case HEAD: - if (state.wrap === 0) { - state.mode = TYPEDO; - break; - } - //=== NEEDBITS(16); - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ - state.check = 0/*crc32(0L, Z_NULL, 0)*/; - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = FLAGS; - break; - } - state.flags = 0; /* expect zlib header */ - if (state.head) { - state.head.done = false; - } - if (!(state.wrap & 1) || /* check if zlib header allowed */ - (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { - strm.msg = 'incorrect header check'; - state.mode = BAD; - break; - } - if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// - len = (hold & 0x0f)/*BITS(4)*/ + 8; - if (state.wbits === 0) { - state.wbits = len; - } - else if (len > state.wbits) { - strm.msg = 'invalid window size'; - state.mode = BAD; - break; - } - state.dmax = 1 << len; - //Tracev((stderr, "inflate: zlib header ok\n")); - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = hold & 0x200 ? DICTID : TYPE; - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - break; - case FLAGS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.flags = hold; - if ((state.flags & 0xff) !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - if (state.flags & 0xe000) { - strm.msg = 'unknown header flags set'; - state.mode = BAD; - break; - } - if (state.head) { - state.head.text = ((hold >> 8) & 1); - } - if (state.flags & 0x0200) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = TIME; - /* falls through */ - case TIME: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.time = hold; - } - if (state.flags & 0x0200) { - //=== CRC4(state.check, hold) - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - hbuf[2] = (hold >>> 16) & 0xff; - hbuf[3] = (hold >>> 24) & 0xff; - state.check = crc32(state.check, hbuf, 4, 0); - //=== - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = OS; - /* falls through */ - case OS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.xflags = (hold & 0xff); - state.head.os = (hold >> 8); - } - if (state.flags & 0x0200) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = EXLEN; - /* falls through */ - case EXLEN: - if (state.flags & 0x0400) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length = hold; - if (state.head) { - state.head.extra_len = hold; - } - if (state.flags & 0x0200) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - else if (state.head) { - state.head.extra = null/*Z_NULL*/; - } - state.mode = EXTRA; - /* falls through */ - case EXTRA: - if (state.flags & 0x0400) { - copy = state.length; - if (copy > have) { copy = have; } - if (copy) { - if (state.head) { - len = state.head.extra_len - state.length; - if (!state.head.extra) { - // Use untyped array for more conveniend processing later - state.head.extra = new Array(state.head.extra_len); - } - utils.arraySet( - state.head.extra, - input, - next, - // extra field is limited to 65536 bytes - // - no need for additional size check - copy, - /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ - len - ); - //zmemcpy(state.head.extra + len, next, - // len + copy > state.head.extra_max ? - // state.head.extra_max - len : copy); - } - if (state.flags & 0x0200) { - state.check = crc32(state.check, input, copy, next); - } - have -= copy; - next += copy; - state.length -= copy; - } - if (state.length) { break inf_leave; } - } - state.length = 0; - state.mode = NAME; - /* falls through */ - case NAME: - if (state.flags & 0x0800) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - // TODO: 2 or 1 bytes? - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.name_max*/)) { - state.head.name += String.fromCharCode(len); - } - } while (len && copy < have); - - if (state.flags & 0x0200) { - state.check = crc32(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.name = null; - } - state.length = 0; - state.mode = COMMENT; - /* falls through */ - case COMMENT: - if (state.flags & 0x1000) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.comm_max*/)) { - state.head.comment += String.fromCharCode(len); - } - } while (len && copy < have); - if (state.flags & 0x0200) { - state.check = crc32(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.comment = null; - } - state.mode = HCRC; - /* falls through */ - case HCRC: - if (state.flags & 0x0200) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (hold !== (state.check & 0xffff)) { - strm.msg = 'header crc mismatch'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - if (state.head) { - state.head.hcrc = ((state.flags >> 9) & 1); - state.head.done = true; - } - strm.adler = state.check = 0; - state.mode = TYPE; - break; - case DICTID: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - strm.adler = state.check = zswap32(hold); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = DICT; - /* falls through */ - case DICT: - if (state.havedict === 0) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - return Z_NEED_DICT; - } - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = TYPE; - /* falls through */ - case TYPE: - if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } - /* falls through */ - case TYPEDO: - if (state.last) { - //--- BYTEBITS() ---// - hold >>>= bits & 7; - bits -= bits & 7; - //---// - state.mode = CHECK; - break; - } - //=== NEEDBITS(3); */ - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.last = (hold & 0x01)/*BITS(1)*/; - //--- DROPBITS(1) ---// - hold >>>= 1; - bits -= 1; - //---// - - switch ((hold & 0x03)/*BITS(2)*/) { - case 0: /* stored block */ - //Tracev((stderr, "inflate: stored block%s\n", - // state.last ? " (last)" : "")); - state.mode = STORED; - break; - case 1: /* fixed block */ - fixedtables(state); - //Tracev((stderr, "inflate: fixed codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = LEN_; /* decode codes */ - if (flush === Z_TREES) { - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break inf_leave; - } - break; - case 2: /* dynamic block */ - //Tracev((stderr, "inflate: dynamic codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = TABLE; - break; - case 3: - strm.msg = 'invalid block type'; - state.mode = BAD; - } - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break; - case STORED: - //--- BYTEBITS() ---// /* go to byte boundary */ - hold >>>= bits & 7; - bits -= bits & 7; - //---// - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { - strm.msg = 'invalid stored block lengths'; - state.mode = BAD; - break; - } - state.length = hold & 0xffff; - //Tracev((stderr, "inflate: stored length %u\n", - // state.length)); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = COPY_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case COPY_: - state.mode = COPY; - /* falls through */ - case COPY: - copy = state.length; - if (copy) { - if (copy > have) { copy = have; } - if (copy > left) { copy = left; } - if (copy === 0) { break inf_leave; } - //--- zmemcpy(put, next, copy); --- - utils.arraySet(output, input, next, copy, put); - //---// - have -= copy; - next += copy; - left -= copy; - put += copy; - state.length -= copy; - break; - } - //Tracev((stderr, "inflate: stored end\n")); - state.mode = TYPE; - break; - case TABLE: - //=== NEEDBITS(14); */ - while (bits < 14) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// -//#ifndef PKZIP_BUG_WORKAROUND - if (state.nlen > 286 || state.ndist > 30) { - strm.msg = 'too many length or distance symbols'; - state.mode = BAD; - break; - } -//#endif - //Tracev((stderr, "inflate: table sizes ok\n")); - state.have = 0; - state.mode = LENLENS; - /* falls through */ - case LENLENS: - while (state.have < state.ncode) { - //=== NEEDBITS(3); - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - while (state.have < 19) { - state.lens[order[state.have++]] = 0; - } - // We have separate tables & no pointers. 2 commented lines below not needed. - //state.next = state.codes; - //state.lencode = state.next; - // Switch to use dynamic table - state.lencode = state.lendyn; - state.lenbits = 7; - - opts = { bits: state.lenbits }; - ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); - state.lenbits = opts.bits; - - if (ret) { - strm.msg = 'invalid code lengths set'; - state.mode = BAD; - break; - } - //Tracev((stderr, "inflate: code lengths ok\n")); - state.have = 0; - state.mode = CODELENS; - /* falls through */ - case CODELENS: - while (state.have < state.nlen + state.ndist) { - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_val < 16) { - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.lens[state.have++] = here_val; - } - else { - if (here_val === 16) { - //=== NEEDBITS(here.bits + 2); - n = here_bits + 2; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - if (state.have === 0) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - len = state.lens[state.have - 1]; - copy = 3 + (hold & 0x03);//BITS(2); - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - } - else if (here_val === 17) { - //=== NEEDBITS(here.bits + 3); - n = here_bits + 3; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 3 + (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - else { - //=== NEEDBITS(here.bits + 7); - n = here_bits + 7; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 11 + (hold & 0x7f);//BITS(7); - //--- DROPBITS(7) ---// - hold >>>= 7; - bits -= 7; - //---// - } - if (state.have + copy > state.nlen + state.ndist) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - while (copy--) { - state.lens[state.have++] = len; - } - } - } - - /* handle error breaks in while */ - if (state.mode === BAD) { break; } - - /* check for end-of-block code (better have one) */ - if (state.lens[256] === 0) { - strm.msg = 'invalid code -- missing end-of-block'; - state.mode = BAD; - break; - } - - /* build code tables -- note: do not change the lenbits or distbits - values here (9 and 6) without reading the comments in inftrees.h - concerning the ENOUGH constants, which depend on those values */ - state.lenbits = 9; - - opts = { bits: state.lenbits }; - ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.lenbits = opts.bits; - // state.lencode = state.next; - - if (ret) { - strm.msg = 'invalid literal/lengths set'; - state.mode = BAD; - break; - } - - state.distbits = 6; - //state.distcode.copy(state.codes); - // Switch to use dynamic table - state.distcode = state.distdyn; - opts = { bits: state.distbits }; - ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.distbits = opts.bits; - // state.distcode = state.next; - - if (ret) { - strm.msg = 'invalid distances set'; - state.mode = BAD; - break; - } - //Tracev((stderr, 'inflate: codes ok\n')); - state.mode = LEN_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case LEN_: - state.mode = LEN; - /* falls through */ - case LEN: - if (have >= 6 && left >= 258) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - inflate_fast(strm, _out); - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - if (state.mode === TYPE) { - state.back = -1; - } - break; - } - state.back = 0; - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if (here_bits <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_op && (here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.lencode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - state.length = here_val; - if (here_op === 0) { - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - state.mode = LIT; - break; - } - if (here_op & 32) { - //Tracevv((stderr, "inflate: end of block\n")); - state.back = -1; - state.mode = TYPE; - break; - } - if (here_op & 64) { - strm.msg = 'invalid literal/length code'; - state.mode = BAD; - break; - } - state.extra = here_op & 15; - state.mode = LENEXT; - /* falls through */ - case LENEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } - //Tracevv((stderr, "inflate: length %u\n", state.length)); - state.was = state.length; - state.mode = DIST; - /* falls through */ - case DIST: - for (;;) { - here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if ((here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.distcode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - if (here_op & 64) { - strm.msg = 'invalid distance code'; - state.mode = BAD; - break; - } - state.offset = here_val; - state.extra = (here_op) & 15; - state.mode = DISTEXT; - /* falls through */ - case DISTEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } -//#ifdef INFLATE_STRICT - if (state.offset > state.dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -//#endif - //Tracevv((stderr, "inflate: distance %u\n", state.offset)); - state.mode = MATCH; - /* falls through */ - case MATCH: - if (left === 0) { break inf_leave; } - copy = _out - left; - if (state.offset > copy) { /* copy from window */ - copy = state.offset - copy; - if (copy > state.whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -// (!) This block is disabled in zlib defailts, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// Trace((stderr, "inflate.c too far\n")); -// copy -= state.whave; -// if (copy > state.length) { copy = state.length; } -// if (copy > left) { copy = left; } -// left -= copy; -// state.length -= copy; -// do { -// output[put++] = 0; -// } while (--copy); -// if (state.length === 0) { state.mode = LEN; } -// break; -//#endif - } - if (copy > state.wnext) { - copy -= state.wnext; - from = state.wsize - copy; - } - else { - from = state.wnext - copy; - } - if (copy > state.length) { copy = state.length; } - from_source = state.window; - } - else { /* copy from output */ - from_source = output; - from = put - state.offset; - copy = state.length; - } - if (copy > left) { copy = left; } - left -= copy; - state.length -= copy; - do { - output[put++] = from_source[from++]; - } while (--copy); - if (state.length === 0) { state.mode = LEN; } - break; - case LIT: - if (left === 0) { break inf_leave; } - output[put++] = state.length; - left--; - state.mode = LEN; - break; - case CHECK: - if (state.wrap) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - // Use '|' insdead of '+' to make sure that result is signed - hold |= input[next++] << bits; - bits += 8; - } - //===// - _out -= left; - strm.total_out += _out; - state.total += _out; - if (_out) { - strm.adler = state.check = - /*UPDATE(state.check, put - _out, _out);*/ - (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); - - } - _out = left; - // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too - if ((state.flags ? hold : zswap32(hold)) !== state.check) { - strm.msg = 'incorrect data check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: check matches trailer\n")); - } - state.mode = LENGTH; - /* falls through */ - case LENGTH: - if (state.wrap && state.flags) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (hold !== (state.total & 0xffffffff)) { - strm.msg = 'incorrect length check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: length matches trailer\n")); - } - state.mode = DONE; - /* falls through */ - case DONE: - ret = Z_STREAM_END; - break inf_leave; - case BAD: - ret = Z_DATA_ERROR; - break inf_leave; - case MEM: - return Z_MEM_ERROR; - case SYNC: - /* falls through */ - default: - return Z_STREAM_ERROR; - } - } - - // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" - - /* - Return from inflate(), updating the total counts and the check value. - If there was no progress during the inflate() call, return a buffer - error. Call updatewindow() to create and/or update the window state. - Note: a memory error from inflate() is non-recoverable. - */ - - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - - if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && - (state.mode < CHECK || flush !== Z_FINISH))) { - if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { - state.mode = MEM; - return Z_MEM_ERROR; - } - } - _in -= strm.avail_in; - _out -= strm.avail_out; - strm.total_in += _in; - strm.total_out += _out; - state.total += _out; - if (state.wrap && _out) { - strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ - (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); - } - strm.data_type = state.bits + (state.last ? 64 : 0) + - (state.mode === TYPE ? 128 : 0) + - (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); - if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { - ret = Z_BUF_ERROR; - } - return ret; -} - -function inflateEnd(strm) { - - if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { - return Z_STREAM_ERROR; - } - - var state = strm.state; - if (state.window) { - state.window = null; - } - strm.state = null; - return Z_OK; -} - -function inflateGetHeader(strm, head) { - var state; - - /* check state */ - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } - - /* save header structure */ - state.head = head; - head.done = false; - return Z_OK; -} - -function inflateSetDictionary(strm, dictionary) { - var dictLength = dictionary.length; - - var state; - var dictid; - var ret; - - /* check state */ - if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; } - state = strm.state; - - if (state.wrap !== 0 && state.mode !== DICT) { - return Z_STREAM_ERROR; - } - - /* check for correct dictionary identifier */ - if (state.mode === DICT) { - dictid = 1; /* adler32(0, null, 0)*/ - /* dictid = adler32(dictid, dictionary, dictLength); */ - dictid = adler32(dictid, dictionary, dictLength, 0); - if (dictid !== state.check) { - return Z_DATA_ERROR; - } - } - /* copy dictionary to window using updatewindow(), which will amend the - existing dictionary if appropriate */ - ret = updatewindow(strm, dictionary, dictLength, dictLength); - if (ret) { - state.mode = MEM; - return Z_MEM_ERROR; - } - state.havedict = 1; - // Tracev((stderr, "inflate: dictionary set\n")); - return Z_OK; -} - -exports.inflateReset = inflateReset; -exports.inflateReset2 = inflateReset2; -exports.inflateResetKeep = inflateResetKeep; -exports.inflateInit = inflateInit; -exports.inflateInit2 = inflateInit2; -exports.inflate = inflate; -exports.inflateEnd = inflateEnd; -exports.inflateGetHeader = inflateGetHeader; -exports.inflateSetDictionary = inflateSetDictionary; -exports.inflateInfo = 'pako inflate (from Nodeca project)'; - -/* Not implemented -exports.inflateCopy = inflateCopy; -exports.inflateGetDictionary = inflateGetDictionary; -exports.inflateMark = inflateMark; -exports.inflatePrime = inflatePrime; -exports.inflateSync = inflateSync; -exports.inflateSyncPoint = inflateSyncPoint; -exports.inflateUndermine = inflateUndermine; -*/ - -},{"../utils/common":2,"./adler32":3,"./crc32":4,"./inffast":5,"./inftrees":7}],7:[function(require,module,exports){ -'use strict'; - - -var utils = require('../utils/common'); - -var MAXBITS = 15; -var ENOUGH_LENS = 852; -var ENOUGH_DISTS = 592; -//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -var CODES = 0; -var LENS = 1; -var DISTS = 2; - -var lbase = [ /* Length codes 257..285 base */ - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 -]; - -var lext = [ /* Length codes 257..285 extra */ - 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 -]; - -var dbase = [ /* Distance codes 0..29 base */ - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577, 0, 0 -]; - -var dext = [ /* Distance codes 0..29 extra */ - 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, - 28, 28, 29, 29, 64, 64 -]; - -module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) -{ - var bits = opts.bits; - //here = opts.here; /* table entry for duplication */ - - var len = 0; /* a code's length in bits */ - var sym = 0; /* index of code symbols */ - var min = 0, max = 0; /* minimum and maximum code lengths */ - var root = 0; /* number of index bits for root table */ - var curr = 0; /* number of index bits for current table */ - var drop = 0; /* code bits to drop for sub-table */ - var left = 0; /* number of prefix codes available */ - var used = 0; /* code entries in table used */ - var huff = 0; /* Huffman code */ - var incr; /* for incrementing code, index */ - var fill; /* index for replicating entries */ - var low; /* low bits for current root entry */ - var mask; /* mask for low root bits */ - var next; /* next available space in table */ - var base = null; /* base value table to use */ - var base_index = 0; -// var shoextra; /* extra bits table to use */ - var end; /* use base and extra for symbol > end */ - var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ - var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ - var extra = null; - var extra_index = 0; - - var here_bits, here_op, here_val; - - /* - Process a set of code lengths to create a canonical Huffman code. The - code lengths are lens[0..codes-1]. Each length corresponds to the - symbols 0..codes-1. The Huffman code is generated by first sorting the - symbols by length from short to long, and retaining the symbol order - for codes with equal lengths. Then the code starts with all zero bits - for the first code of the shortest length, and the codes are integer - increments for the same length, and zeros are appended as the length - increases. For the deflate format, these bits are stored backwards - from their more natural integer increment ordering, and so when the - decoding tables are built in the large loop below, the integer codes - are incremented backwards. - - This routine assumes, but does not check, that all of the entries in - lens[] are in the range 0..MAXBITS. The caller must assure this. - 1..MAXBITS is interpreted as that code length. zero means that that - symbol does not occur in this code. - - The codes are sorted by computing a count of codes for each length, - creating from that a table of starting indices for each length in the - sorted table, and then entering the symbols in order in the sorted - table. The sorted table is work[], with that space being provided by - the caller. - - The length counts are used for other purposes as well, i.e. finding - the minimum and maximum length codes, determining if there are any - codes at all, checking for a valid set of lengths, and looking ahead - at length counts to determine sub-table sizes when building the - decoding tables. - */ - - /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ - for (len = 0; len <= MAXBITS; len++) { - count[len] = 0; - } - for (sym = 0; sym < codes; sym++) { - count[lens[lens_index + sym]]++; - } - - /* bound code lengths, force root to be within code lengths */ - root = bits; - for (max = MAXBITS; max >= 1; max--) { - if (count[max] !== 0) { break; } - } - if (root > max) { - root = max; - } - if (max === 0) { /* no symbols to code at all */ - //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ - //table.bits[opts.table_index] = 1; //here.bits = (var char)1; - //table.val[opts.table_index++] = 0; //here.val = (var short)0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - - //table.op[opts.table_index] = 64; - //table.bits[opts.table_index] = 1; - //table.val[opts.table_index++] = 0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - opts.bits = 1; - return 0; /* no symbols, but wait for decoding to report error */ - } - for (min = 1; min < max; min++) { - if (count[min] !== 0) { break; } - } - if (root < min) { - root = min; - } - - /* check for an over-subscribed or incomplete set of lengths */ - left = 1; - for (len = 1; len <= MAXBITS; len++) { - left <<= 1; - left -= count[len]; - if (left < 0) { - return -1; - } /* over-subscribed */ - } - if (left > 0 && (type === CODES || max !== 1)) { - return -1; /* incomplete set */ - } - - /* generate offsets into symbol table for each length for sorting */ - offs[1] = 0; - for (len = 1; len < MAXBITS; len++) { - offs[len + 1] = offs[len] + count[len]; - } - - /* sort symbols by length, by symbol order within each length */ - for (sym = 0; sym < codes; sym++) { - if (lens[lens_index + sym] !== 0) { - work[offs[lens[lens_index + sym]]++] = sym; - } - } - - /* - Create and fill in decoding tables. In this loop, the table being - filled is at next and has curr index bits. The code being used is huff - with length len. That code is converted to an index by dropping drop - bits off of the bottom. For codes where len is less than drop + curr, - those top drop + curr - len bits are incremented through all values to - fill the table with replicated entries. - - root is the number of index bits for the root table. When len exceeds - root, sub-tables are created pointed to by the root entry with an index - of the low root bits of huff. This is saved in low to check for when a - new sub-table should be started. drop is zero when the root table is - being filled, and drop is root when sub-tables are being filled. - - When a new sub-table is needed, it is necessary to look ahead in the - code lengths to determine what size sub-table is needed. The length - counts are used for this, and so count[] is decremented as codes are - entered in the tables. - - used keeps track of how many table entries have been allocated from the - provided *table space. It is checked for LENS and DIST tables against - the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in - the initial root table size constants. See the comments in inftrees.h - for more information. - - sym increments through all symbols, and the loop terminates when - all codes of length max, i.e. all codes, have been processed. This - routine permits incomplete codes, so another loop after this one fills - in the rest of the decoding tables with invalid code markers. - */ - - /* set up for code type */ - // poor man optimization - use if-else instead of switch, - // to avoid deopts in old v8 - if (type === CODES) { - base = extra = work; /* dummy value--not used */ - end = 19; - - } else if (type === LENS) { - base = lbase; - base_index -= 257; - extra = lext; - extra_index -= 257; - end = 256; - - } else { /* DISTS */ - base = dbase; - extra = dext; - end = -1; - } - - /* initialize opts for loop */ - huff = 0; /* starting code */ - sym = 0; /* starting code symbol */ - len = min; /* starting code length */ - next = table_index; /* current table to fill in */ - curr = root; /* current table index bits */ - drop = 0; /* current bits to drop from code for index */ - low = -1; /* trigger new sub-table when len > root */ - used = 1 << root; /* use root table entries */ - mask = used - 1; /* mask for comparing low */ - - /* check available table space */ - if ((type === LENS && used > ENOUGH_LENS) || - (type === DISTS && used > ENOUGH_DISTS)) { - return 1; - } - - var i = 0; - /* process all codes and make table entries */ - for (;;) { - i++; - /* create table entry */ - here_bits = len - drop; - if (work[sym] < end) { - here_op = 0; - here_val = work[sym]; - } - else if (work[sym] > end) { - here_op = extra[extra_index + work[sym]]; - here_val = base[base_index + work[sym]]; - } - else { - here_op = 32 + 64; /* end of block */ - here_val = 0; - } - - /* replicate for those indices with low len bits equal to huff */ - incr = 1 << (len - drop); - fill = 1 << curr; - min = fill; /* save offset to next table */ - do { - fill -= incr; - table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; - } while (fill !== 0); - - /* backwards increment the len-bit code huff */ - incr = 1 << (len - 1); - while (huff & incr) { - incr >>= 1; - } - if (incr !== 0) { - huff &= incr - 1; - huff += incr; - } else { - huff = 0; - } - - /* go to next symbol, update count, len */ - sym++; - if (--count[len] === 0) { - if (len === max) { break; } - len = lens[lens_index + work[sym]]; - } - - /* create new sub-table if needed */ - if (len > root && (huff & mask) !== low) { - /* if first time, transition to sub-tables */ - if (drop === 0) { - drop = root; - } - - /* increment past last table */ - next += min; /* here min is 1 << curr */ - - /* determine length of next table */ - curr = len - drop; - left = 1 << curr; - while (curr + drop < max) { - left -= count[curr + drop]; - if (left <= 0) { break; } - curr++; - left <<= 1; - } - - /* check for enough space */ - used += 1 << curr; - if ((type === LENS && used > ENOUGH_LENS) || - (type === DISTS && used > ENOUGH_DISTS)) { - return 1; - } - - /* point entry in root table to sub-table */ - low = huff & mask; - /*table.op[low] = curr; - table.bits[low] = root; - table.val[low] = next - opts.table_index;*/ - table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; - } - } - - /* fill in remaining table entry if code is incomplete (guaranteed to have - at most one remaining entry, since if the code is incomplete, the - maximum code length that was allowed to get this far is one bit) */ - if (huff !== 0) { - //table.op[next + huff] = 64; /* invalid code marker */ - //table.bits[next + huff] = len - drop; - //table.val[next + huff] = 0; - table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; - } - - /* set return parameters */ - //opts.table_index += used; - opts.bits = root; - return 0; -}; - -},{"../utils/common":2}],8:[function(require,module,exports){ -'use strict'; - - -function ZStream() { - /* next input byte */ - this.input = null; // JS specific, because we have no pointers - this.next_in = 0; - /* number of bytes available at input */ - this.avail_in = 0; - /* total number of input bytes read so far */ - this.total_in = 0; - /* next output byte should be put there */ - this.output = null; // JS specific, because we have no pointers - this.next_out = 0; - /* remaining free space at output */ - this.avail_out = 0; - /* total number of bytes output so far */ - this.total_out = 0; - /* last error message, NULL if no error */ - this.msg = ''/*Z_NULL*/; - /* not visible by applications */ - this.state = null; - /* best guess about the data type: binary or text */ - this.data_type = 2/*Z_UNKNOWN*/; - /* adler32 value of the uncompressed data */ - this.adler = 0; -} - -module.exports = ZStream; - -},{}]},{},[1])(1) -}); \ No newline at end of file diff --git a/core/inflator.mod.js b/core/inflator.mod.js deleted file mode 100644 index 26e6441b..00000000 --- a/core/inflator.mod.js +++ /dev/null @@ -1,40 +0,0 @@ -var zlib = require('pako/lib/zlib/inflate.js'); -var ZStream = require('pako/lib/zlib/zstream.js'); - -function Inflate() { - this.strm = new ZStream(); - this.chunkSize = 1024 * 10 * 10; - this.strm.output = new Uint8Array(this.chunkSize); - this.windowBits = 5; - - zlib.inflateInit(this.strm, this.windowBits); -}; - -Inflate.prototype = { - inflate: function (data, flush, expected) { - this.strm.input = data; - this.strm.avail_in = this.strm.input.length; - this.strm.next_in = 0; - this.strm.next_out = 0; - - // resize our output buffer if it's too small - // (we could just use multiple chunks, but that would cause an extra - // allocation each time to flatten the chunks) - if (expected > this.chunkSize) { - this.chunkSize = expected; - this.strm.output = new Uint8Array(this.chunkSize); - } - - this.strm.avail_out = this.chunkSize; - - zlib.inflate(this.strm, flush); - - return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); - }, - - reset: function () { - zlib.inflateReset(this.strm); - } -}; - -module.exports = { Inflate: Inflate }; diff --git a/core/rfb.js b/core/rfb.js index caf96cfd..97667b43 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -18,7 +18,7 @@ import Base64 from "./base64.js"; import DES from "./des.js"; import KeyTable from "./input/keysym.js"; import XtScancode from "./input/xtscancodes.js"; -import Inflator from "./inflator.mod.js"; +import Inflator from "./inflator.js"; /*jslint white: false, browser: true */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */ @@ -423,7 +423,7 @@ export default function RFB(defaults) { } for (i = 0; i < 4; i++) { - this._FBU.zlibs[i] = new Inflator.Inflate(); + this._FBU.zlibs[i] = new Inflator(); } }, diff --git a/package.json b/package.json index 52f76f6d..93f6a894 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,5 @@ "spooky": "^0.2.5", "temp": "^0.8.3", "through2": "^2.0.1" - }, - "dependencies": { - "pako": "^1.0.3" } } diff --git a/vendor/pako/LICENSE b/vendor/pako/LICENSE new file mode 100644 index 00000000..d082ae32 --- /dev/null +++ b/vendor/pako/LICENSE @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (C) 2014-2016 by Vitaly Puzrin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/pako/README.md b/vendor/pako/README.md new file mode 100644 index 00000000..755df643 --- /dev/null +++ b/vendor/pako/README.md @@ -0,0 +1,6 @@ +This is an ES6-modules-compatible version of +https://github.com/nodeca/pako, based on pako version 1.0.3. + +It's more-or-less a direct translation of the original, with unused parts +removed, and the dynamic support for non-typed arrays removed (since ES6 +modules don't work well with dynamic exports). diff --git a/vendor/pako/lib/utils/common.js b/vendor/pako/lib/utils/common.js new file mode 100644 index 00000000..db7a31ea --- /dev/null +++ b/vendor/pako/lib/utils/common.js @@ -0,0 +1,45 @@ +// reduce buffer size, avoiding mem copy +export function shrinkBuf (buf, size) { + if (buf.length === size) { return buf; } + if (buf.subarray) { return buf.subarray(0, size); } + buf.length = size; + return buf; +}; + + +export function arraySet (dest, src, src_offs, len, dest_offs) { + if (src.subarray && dest.subarray) { + dest.set(src.subarray(src_offs, src_offs + len), dest_offs); + return; + } + // Fallback to ordinary array + for (var i = 0; i < len; i++) { + dest[dest_offs + i] = src[src_offs + i]; + } +} + +// Join array of chunks to single array. +export function flattenChunks (chunks) { + var i, l, len, pos, chunk, result; + + // calculate data length + len = 0; + for (i = 0, l = chunks.length; i < l; i++) { + len += chunks[i].length; + } + + // join chunks + result = new Uint8Array(len); + pos = 0; + for (i = 0, l = chunks.length; i < l; i++) { + chunk = chunks[i]; + result.set(chunk, pos); + pos += chunk.length; + } + + return result; +} + +export const Buf8 = Uint8Array; +export const Buf16 = Uint16Array; +export const Buf32 = Int32Array; diff --git a/vendor/pako/lib/zlib/adler32.js b/vendor/pako/lib/zlib/adler32.js new file mode 100644 index 00000000..058a534a --- /dev/null +++ b/vendor/pako/lib/zlib/adler32.js @@ -0,0 +1,27 @@ +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It doesn't worth to make additional optimizationa as in original. +// Small size is preferable. + +export default function adler32(adler, buf, len, pos) { + var s1 = (adler & 0xffff) |0, + s2 = ((adler >>> 16) & 0xffff) |0, + n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) |0; +} diff --git a/vendor/pako/lib/zlib/constants.js b/vendor/pako/lib/zlib/constants.js new file mode 100644 index 00000000..7d80502d --- /dev/null +++ b/vendor/pako/lib/zlib/constants.js @@ -0,0 +1,47 @@ +export default { + + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + //Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + //Z_VERSION_ERROR: -6, + + /* compression levels */ + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + + + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + + /* Possible values of the data_type field (though see inflate()) */ + Z_BINARY: 0, + Z_TEXT: 1, + //Z_ASCII: 1, // = Z_TEXT (deprecated) + Z_UNKNOWN: 2, + + /* The deflate compression method */ + Z_DEFLATED: 8 + //Z_NULL: null // Use -1 or null inline, depending on var type +}; diff --git a/vendor/pako/lib/zlib/crc32.js b/vendor/pako/lib/zlib/crc32.js new file mode 100644 index 00000000..611ffb29 --- /dev/null +++ b/vendor/pako/lib/zlib/crc32.js @@ -0,0 +1,36 @@ +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + + +// Use ordinary array, since untyped makes no boost here +export default function makeTable() { + var c, table = []; + + for (var n = 0; n < 256; n++) { + c = n; + for (var k = 0; k < 8; k++) { + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +} + +// Create table on load. Just 255 signed longs. Not a problem. +var crcTable = makeTable(); + + +function crc32(crc, buf, len, pos) { + var t = crcTable, + end = pos + len; + + crc ^= -1; + + for (var i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} diff --git a/vendor/pako/lib/zlib/deflate.js b/vendor/pako/lib/zlib/deflate.js new file mode 100644 index 00000000..ec3666d7 --- /dev/null +++ b/vendor/pako/lib/zlib/deflate.js @@ -0,0 +1,1846 @@ +import * as utils from "../utils/common.js"; +import * as trees from "./trees.js"; +import adler32 from "./adler32.js"; +import crc32 from "./crc32.js"; +import msg from "./messages.js"; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +/* Allowed flush values; see deflate() and inflate() below for details */ +var Z_NO_FLUSH = 0; +var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +var Z_FULL_FLUSH = 3; +var Z_FINISH = 4; +var Z_BLOCK = 5; +//var Z_TREES = 6; + + +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ +var Z_OK = 0; +var Z_STREAM_END = 1; +//var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; +var Z_STREAM_ERROR = -2; +var Z_DATA_ERROR = -3; +//var Z_MEM_ERROR = -4; +var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + + +/* compression levels */ +//var Z_NO_COMPRESSION = 0; +//var Z_BEST_SPEED = 1; +//var Z_BEST_COMPRESSION = 9; +var Z_DEFAULT_COMPRESSION = -1; + + +var Z_FILTERED = 1; +var Z_HUFFMAN_ONLY = 2; +var Z_RLE = 3; +var Z_FIXED = 4; +var Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +//var Z_BINARY = 0; +//var Z_TEXT = 1; +//var Z_ASCII = 1; // = Z_TEXT +var Z_UNKNOWN = 2; + + +/* The deflate compression method */ +var Z_DEFLATED = 8; + +/*============================================================================*/ + + +var MAX_MEM_LEVEL = 9; +/* Maximum value for memLevel in deflateInit2 */ +var MAX_WBITS = 15; +/* 32K LZ77 window */ +var DEF_MEM_LEVEL = 8; + + +var LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ +var LITERALS = 256; +/* number of literal bytes 0..255 */ +var L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ +var D_CODES = 30; +/* number of distance codes */ +var BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ +var HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ +var MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +var MIN_MATCH = 3; +var MAX_MATCH = 258; +var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + +var PRESET_DICT = 0x20; + +var INIT_STATE = 42; +var EXTRA_STATE = 69; +var NAME_STATE = 73; +var COMMENT_STATE = 91; +var HCRC_STATE = 103; +var BUSY_STATE = 113; +var FINISH_STATE = 666; + +var BS_NEED_MORE = 1; /* block not completed, need more input or more output */ +var BS_BLOCK_DONE = 2; /* block flush performed */ +var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ +var BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ + +var OS_CODE = 0x03; // Unix :) . Don't detect, use this default. + +function err(strm, errorCode) { + strm.msg = msg[errorCode]; + return errorCode; +} + +function rank(f) { + return ((f) << 1) - ((f) > 4 ? 9 : 0); +} + +function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } + + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->output buffer and copying into it. + * (See also read_buf()). + */ +function flush_pending(strm) { + var s = strm.state; + + //_tr_flush_bits(s); + var len = s.pending; + if (len > strm.avail_out) { + len = strm.avail_out; + } + if (len === 0) { return; } + + utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out); + strm.next_out += len; + s.pending_out += len; + strm.total_out += len; + strm.avail_out -= len; + s.pending -= len; + if (s.pending === 0) { + s.pending_out = 0; + } +} + + +function flush_block_only(s, last) { + trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); + s.block_start = s.strstart; + flush_pending(s.strm); +} + + +function put_byte(s, b) { + s.pending_buf[s.pending++] = b; +} + + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +function putShortMSB(s, b) { +// put_byte(s, (Byte)(b >> 8)); +// put_byte(s, (Byte)(b & 0xff)); + s.pending_buf[s.pending++] = (b >>> 8) & 0xff; + s.pending_buf[s.pending++] = b & 0xff; +} + + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->input buffer and copying from it. + * (See also flush_pending()). + */ +function read_buf(strm, buf, start, size) { + var len = strm.avail_in; + + if (len > size) { len = size; } + if (len === 0) { return 0; } + + strm.avail_in -= len; + + // zmemcpy(buf, strm->next_in, len); + utils.arraySet(buf, strm.input, strm.next_in, len, start); + if (strm.state.wrap === 1) { + strm.adler = adler32(strm.adler, buf, len, start); + } + + else if (strm.state.wrap === 2) { + strm.adler = crc32(strm.adler, buf, len, start); + } + + strm.next_in += len; + strm.total_in += len; + + return len; +} + + +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +function longest_match(s, cur_match) { + var chain_length = s.max_chain_length; /* max hash chain length */ + var scan = s.strstart; /* current string */ + var match; /* matched string */ + var len; /* length of current match */ + var best_len = s.prev_length; /* best match length so far */ + var nice_match = s.nice_match; /* stop if match long enough */ + var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? + s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; + + var _win = s.window; // shortcut + + var wmask = s.w_mask; + var prev = s.prev; + + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + + var strend = s.strstart + MAX_MATCH; + var scan_end1 = _win[scan + best_len - 1]; + var scan_end = _win[scan + best_len]; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s.prev_length >= s.good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (nice_match > s.lookahead) { nice_match = s.lookahead; } + + // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + // Assert(cur_match < s->strstart, "no future"); + match = cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ + + if (_win[match + best_len] !== scan_end || + _win[match + best_len - 1] !== scan_end1 || + _win[match] !== _win[scan] || + _win[++match] !== _win[scan + 1]) { + continue; + } + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2; + match++; + // Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + /*jshint noempty:false*/ + } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + scan < strend); + + // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + s.match_start = cur_match; + best_len = len; + if (len >= nice_match) { + break; + } + scan_end1 = _win[scan + best_len - 1]; + scan_end = _win[scan + best_len]; + } + } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); + + if (best_len <= s.lookahead) { + return best_len; + } + return s.lookahead; +} + + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +function fill_window(s) { + var _w_size = s.w_size; + var p, n, m, more, str; + + //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = s.window_size - s.lookahead - s.strstart; + + // JS ints have 32 bit, block below not needed + /* Deal with !@#$% 64K limit: */ + //if (sizeof(int) <= 2) { + // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + // more = wsize; + // + // } else if (more == (unsigned)(-1)) { + // /* Very unlikely, but possible on 16 bit machine if + // * strstart == 0 && lookahead == 1 (input done a byte at time) + // */ + // more--; + // } + //} + + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { + + utils.arraySet(s.window, s.window, _w_size, _w_size, 0); + s.match_start -= _w_size; + s.strstart -= _w_size; + /* we now have strstart >= MAX_DIST */ + s.block_start -= _w_size; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + + n = s.hash_size; + p = n; + do { + m = s.head[--p]; + s.head[p] = (m >= _w_size ? m - _w_size : 0); + } while (--n); + + n = _w_size; + p = n; + do { + m = s.prev[--p]; + s.prev[p] = (m >= _w_size ? m - _w_size : 0); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); + + more += _w_size; + } + if (s.strm.avail_in === 0) { + break; + } + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + //Assert(more >= 2, "more < 2"); + n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); + s.lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s.lookahead + s.insert >= MIN_MATCH) { + str = s.strstart - s.insert; + s.ins_h = s.window[str]; + + /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask; +//#if MIN_MATCH != 3 +// Call update_hash() MIN_MATCH-3 more times +//#endif + while (s.insert) { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = str; + str++; + s.insert--; + if (s.lookahead + s.insert < MIN_MATCH) { + break; + } + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ +// if (s.high_water < s.window_size) { +// var curr = s.strstart + s.lookahead; +// var init = 0; +// +// if (s.high_water < curr) { +// /* Previous high water mark below current data -- zero WIN_INIT +// * bytes or up to end of window, whichever is less. +// */ +// init = s.window_size - curr; +// if (init > WIN_INIT) +// init = WIN_INIT; +// zmemzero(s->window + curr, (unsigned)init); +// s->high_water = curr + init; +// } +// else if (s->high_water < (ulg)curr + WIN_INIT) { +// /* High water mark at or above current data, but below current data +// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up +// * to end of window, whichever is less. +// */ +// init = (ulg)curr + WIN_INIT - s->high_water; +// if (init > s->window_size - s->high_water) +// init = s->window_size - s->high_water; +// zmemzero(s->window + s->high_water, (unsigned)init); +// s->high_water += init; +// } +// } +// +// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, +// "not enough room for search"); +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +function deflate_stored(s, flush) { + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + var max_block_size = 0xffff; + + if (max_block_size > s.pending_buf_size - 5) { + max_block_size = s.pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s.lookahead <= 1) { + + //Assert(s->strstart < s->w_size+MAX_DIST(s) || + // s->block_start >= (long)s->w_size, "slide too late"); +// if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) || +// s.block_start >= s.w_size)) { +// throw new Error("slide too late"); +// } + + fill_window(s); + if (s.lookahead === 0 && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + + if (s.lookahead === 0) { + break; + } + /* flush the current block */ + } + //Assert(s->block_start >= 0L, "block gone"); +// if (s.block_start < 0) throw new Error("block gone"); + + s.strstart += s.lookahead; + s.lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + var max_start = s.block_start + max_block_size; + + if (s.strstart === 0 || s.strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s.lookahead = s.strstart - max_start; + s.strstart = max_start; + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + + + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + + s.insert = 0; + + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + + if (s.strstart > s.block_start) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_NEED_MORE; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +function deflate_fast(s, flush) { + var hash_head; /* head of the hash chain */ + var bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { + break; /* flush the current block */ + } + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + } + if (s.match_length >= MIN_MATCH) { + // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only + + /*** _tr_tally_dist(s, s.strstart - s.match_start, + s.match_length - MIN_MATCH, bflush); ***/ + bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ + if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { + s.match_length--; /* string at strstart already in table */ + do { + s.strstart++; + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s.match_length !== 0); + s.strstart++; + } else + { + s.strstart += s.match_length; + s.match_length = 0; + s.ins_h = s.window[s.strstart]; + /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask; + +//#if MIN_MATCH != 3 +// Call UPDATE_HASH() MIN_MATCH-3 more times +//#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s.window[s.strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +function deflate_slow(s, flush) { + var hash_head; /* head of hash chain */ + var bflush; /* set if current block must be flushed */ + + var max_insert; + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + */ + s.prev_length = s.match_length; + s.prev_match = s.match_start; + s.match_length = MIN_MATCH - 1; + + if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && + s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + + if (s.match_length <= 5 && + (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s.match_length = MIN_MATCH - 1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { + max_insert = s.strstart + s.lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + //check_match(s, s.strstart-1, s.prev_match, s.prev_length); + + /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, + s.prev_length - MIN_MATCH, bflush);***/ + bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s.lookahead -= s.prev_length - 1; + s.prev_length -= 2; + do { + if (++s.strstart <= max_insert) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + } while (--s.prev_length !== 0); + s.match_available = 0; + s.match_length = MIN_MATCH - 1; + s.strstart++; + + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + } else if (s.match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); + + if (bflush) { + /*** FLUSH_BLOCK_ONLY(s, 0) ***/ + flush_block_only(s, false); + /***/ + } + s.strstart++; + s.lookahead--; + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s.match_available = 1; + s.strstart++; + s.lookahead--; + } + } + //Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s.match_available) { + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); + + s.match_available = 0; + } + s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_BLOCK_DONE; +} + + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +function deflate_rle(s, flush) { + var bflush; /* set if current block must be flushed */ + var prev; /* byte at distance one to match */ + var scan, strend; /* scan goes up to strend for length of run */ + + var _win = s.window; + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s.lookahead <= MAX_MATCH) { + fill_window(s); + if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s.match_length = 0; + if (s.lookahead >= MIN_MATCH && s.strstart > 0) { + scan = s.strstart - 1; + prev = _win[scan]; + if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { + strend = s.strstart + MAX_MATCH; + do { + /*jshint noempty:false*/ + } while (prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + scan < strend); + s.match_length = MAX_MATCH - (strend - scan); + if (s.match_length > s.lookahead) { + s.match_length = s.lookahead; + } + } + //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s.match_length >= MIN_MATCH) { + //check_match(s, s.strstart, s.strstart - 1, s.match_length); + + /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ + bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + s.strstart += s.match_length; + s.match_length = 0; + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +function deflate_huff(s, flush) { + var bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s.lookahead === 0) { + fill_window(s); + if (s.lookahead === 0) { + if (flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s.match_length = 0; + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + s.lookahead--; + s.strstart++; + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +function Config(good_length, max_lazy, nice_length, max_chain, func) { + this.good_length = good_length; + this.max_lazy = max_lazy; + this.nice_length = nice_length; + this.max_chain = max_chain; + this.func = func; +} + +var configuration_table; + +configuration_table = [ + /* good lazy nice chain */ + new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ + new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ + new Config(4, 5, 16, 8, deflate_fast), /* 2 */ + new Config(4, 6, 32, 32, deflate_fast), /* 3 */ + + new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ + new Config(8, 16, 32, 32, deflate_slow), /* 5 */ + new Config(8, 16, 128, 128, deflate_slow), /* 6 */ + new Config(8, 32, 128, 256, deflate_slow), /* 7 */ + new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ + new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ +]; + + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +function lm_init(s) { + s.window_size = 2 * s.w_size; + + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + + /* Set the default configuration parameters: + */ + s.max_lazy_match = configuration_table[s.level].max_lazy; + s.good_match = configuration_table[s.level].good_length; + s.nice_match = configuration_table[s.level].nice_length; + s.max_chain_length = configuration_table[s.level].max_chain; + + s.strstart = 0; + s.block_start = 0; + s.lookahead = 0; + s.insert = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + s.ins_h = 0; +} + + +function DeflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.status = 0; /* as the name implies */ + this.pending_buf = null; /* output still pending */ + this.pending_buf_size = 0; /* size of pending_buf */ + this.pending_out = 0; /* next pending byte to output to the stream */ + this.pending = 0; /* nb of bytes in the pending buffer */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.gzhead = null; /* gzip header information to write */ + this.gzindex = 0; /* where in extra, name, or comment */ + this.method = Z_DEFLATED; /* can only be DEFLATED */ + this.last_flush = -1; /* value of flush param for previous deflate call */ + + this.w_size = 0; /* LZ77 window size (32K by default) */ + this.w_bits = 0; /* log2(w_size) (8..16) */ + this.w_mask = 0; /* w_size - 1 */ + + this.window = null; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. + */ + + this.window_size = 0; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + this.prev = null; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + this.head = null; /* Heads of the hash chains or NIL. */ + + this.ins_h = 0; /* hash index of string to be inserted */ + this.hash_size = 0; /* number of elements in hash table */ + this.hash_bits = 0; /* log2(hash_size) */ + this.hash_mask = 0; /* hash_size-1 */ + + this.hash_shift = 0; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + this.block_start = 0; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + this.match_length = 0; /* length of best match */ + this.prev_match = 0; /* previous match */ + this.match_available = 0; /* set if previous match exists */ + this.strstart = 0; /* start of string to insert */ + this.match_start = 0; /* start of matching string */ + this.lookahead = 0; /* number of valid bytes ahead in window */ + + this.prev_length = 0; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + this.max_chain_length = 0; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + this.max_lazy_match = 0; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + // That's alias to max_lazy_match, don't use directly + //this.max_insert_length = 0; + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + this.level = 0; /* compression level (1..9) */ + this.strategy = 0; /* favor or force Huffman coding*/ + + this.good_match = 0; + /* Use a faster search when the previous match is longer than this */ + + this.nice_match = 0; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + + /* Didn't use ct_data typedef below to suppress compiler warning */ + + // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + // Use flat array of DOUBLE size, with interleaved fata, + // because JS does not support effective + this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2); + this.dyn_dtree = new utils.Buf16((2 * D_CODES + 1) * 2); + this.bl_tree = new utils.Buf16((2 * BL_CODES + 1) * 2); + zero(this.dyn_ltree); + zero(this.dyn_dtree); + zero(this.bl_tree); + + this.l_desc = null; /* desc. for literal tree */ + this.d_desc = null; /* desc. for distance tree */ + this.bl_desc = null; /* desc. for bit length tree */ + + //ush bl_count[MAX_BITS+1]; + this.bl_count = new utils.Buf16(MAX_BITS + 1); + /* number of codes at each bit length for an optimal tree */ + + //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + this.heap = new utils.Buf16(2 * L_CODES + 1); /* heap used to build the Huffman trees */ + zero(this.heap); + + this.heap_len = 0; /* number of elements in the heap */ + this.heap_max = 0; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; + zero(this.depth); + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + this.l_buf = 0; /* buffer index for literals or lengths */ + + this.lit_bufsize = 0; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + this.last_lit = 0; /* running index in l_buf */ + + this.d_buf = 0; + /* Buffer index for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + this.opt_len = 0; /* bit length of current block with optimal trees */ + this.static_len = 0; /* bit length of current block with static trees */ + this.matches = 0; /* number of string matches in current block */ + this.insert = 0; /* bytes at end of window left to insert */ + + + this.bi_buf = 0; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + this.bi_valid = 0; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + // Used for window memory init. We safely ignore it for JS. That makes + // sense only for pointers and memory check tools. + //this.high_water = 0; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ +} + + +function deflateResetKeep(strm) { + var s; + + if (!strm || !strm.state) { + return err(strm, Z_STREAM_ERROR); + } + + strm.total_in = strm.total_out = 0; + strm.data_type = Z_UNKNOWN; + + s = strm.state; + s.pending = 0; + s.pending_out = 0; + + if (s.wrap < 0) { + s.wrap = -s.wrap; + /* was made negative by deflate(..., Z_FINISH); */ + } + s.status = (s.wrap ? INIT_STATE : BUSY_STATE); + strm.adler = (s.wrap === 2) ? + 0 // crc32(0, Z_NULL, 0) + : + 1; // adler32(0, Z_NULL, 0) + s.last_flush = Z_NO_FLUSH; + trees._tr_init(s); + return Z_OK; +} + + +function deflateReset(strm) { + var ret = deflateResetKeep(strm); + if (ret === Z_OK) { + lm_init(strm.state); + } + return ret; +} + + +function deflateSetHeader(strm, head) { + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; } + strm.state.gzhead = head; + return Z_OK; +} + + +function deflateInit2(strm, level, method, windowBits, memLevel, strategy) { + if (!strm) { // === Z_NULL + return Z_STREAM_ERROR; + } + var wrap = 1; + + if (level === Z_DEFAULT_COMPRESSION) { + level = 6; + } + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } + + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } + + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED) { + return err(strm, Z_STREAM_ERROR); + } + + + if (windowBits === 8) { + windowBits = 9; + } + /* until 256-byte window bug fixed */ + + var s = new DeflateState(); + + strm.state = s; + s.strm = strm; + + s.wrap = wrap; + s.gzhead = null; + s.w_bits = windowBits; + s.w_size = 1 << s.w_bits; + s.w_mask = s.w_size - 1; + + s.hash_bits = memLevel + 7; + s.hash_size = 1 << s.hash_bits; + s.hash_mask = s.hash_size - 1; + s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + s.window = new utils.Buf8(s.w_size * 2); + s.head = new utils.Buf16(s.hash_size); + s.prev = new utils.Buf16(s.w_size); + + // Don't need mem init magic for JS. + //s.high_water = 0; /* nothing written to s->window yet */ + + s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + s.pending_buf_size = s.lit_bufsize * 4; + + //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + //s->pending_buf = (uchf *) overlay; + s.pending_buf = new utils.Buf8(s.pending_buf_size); + + // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) + //s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s.d_buf = 1 * s.lit_bufsize; + + //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + s.l_buf = (1 + 2) * s.lit_bufsize; + + s.level = level; + s.strategy = strategy; + s.method = method; + + return deflateReset(strm); +} + +function deflateInit(strm, level) { + return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); +} + + +function deflate(strm, flush) { + var old_flush, s; + var beg, val; // for gzip header write only + + if (!strm || !strm.state || + flush > Z_BLOCK || flush < 0) { + return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR; + } + + s = strm.state; + + if (!strm.output || + (!strm.input && strm.avail_in !== 0) || + (s.status === FINISH_STATE && flush !== Z_FINISH)) { + return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR); + } + + s.strm = strm; /* just in case */ + old_flush = s.last_flush; + s.last_flush = flush; + + /* Write the header */ + if (s.status === INIT_STATE) { + + if (s.wrap === 2) { // GZIP header + strm.adler = 0; //crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (!s.gzhead) { // s->gzhead == Z_NULL + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s.status = BUSY_STATE; + } + else { + put_byte(s, (s.gzhead.text ? 1 : 0) + + (s.gzhead.hcrc ? 2 : 0) + + (!s.gzhead.extra ? 0 : 4) + + (!s.gzhead.name ? 0 : 8) + + (!s.gzhead.comment ? 0 : 16) + ); + put_byte(s, s.gzhead.time & 0xff); + put_byte(s, (s.gzhead.time >> 8) & 0xff); + put_byte(s, (s.gzhead.time >> 16) & 0xff); + put_byte(s, (s.gzhead.time >> 24) & 0xff); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, s.gzhead.os & 0xff); + if (s.gzhead.extra && s.gzhead.extra.length) { + put_byte(s, s.gzhead.extra.length & 0xff); + put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); + } + if (s.gzhead.hcrc) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0); + } + s.gzindex = 0; + s.status = EXTRA_STATE; + } + } + else // DEFLATE header + { + var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8; + var level_flags = -1; + + if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { + level_flags = 0; + } else if (s.level < 6) { + level_flags = 1; + } else if (s.level === 6) { + level_flags = 2; + } else { + level_flags = 3; + } + header |= (level_flags << 6); + if (s.strstart !== 0) { header |= PRESET_DICT; } + header += 31 - (header % 31); + + s.status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s.strstart !== 0) { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + strm.adler = 1; // adler32(0L, Z_NULL, 0); + } + } + +//#ifdef GZIP + if (s.status === EXTRA_STATE) { + if (s.gzhead.extra/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + + while (s.gzindex < (s.gzhead.extra.length & 0xffff)) { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + break; + } + } + put_byte(s, s.gzhead.extra[s.gzindex] & 0xff); + s.gzindex++; + } + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (s.gzindex === s.gzhead.extra.length) { + s.gzindex = 0; + s.status = NAME_STATE; + } + } + else { + s.status = NAME_STATE; + } + } + if (s.status === NAME_STATE) { + if (s.gzhead.name/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.name.length) { + val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.gzindex = 0; + s.status = COMMENT_STATE; + } + } + else { + s.status = COMMENT_STATE; + } + } + if (s.status === COMMENT_STATE) { + if (s.gzhead.comment/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.comment.length) { + val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.status = HCRC_STATE; + } + } + else { + s.status = HCRC_STATE; + } + } + if (s.status === HCRC_STATE) { + if (s.gzhead.hcrc) { + if (s.pending + 2 > s.pending_buf_size) { + flush_pending(strm); + } + if (s.pending + 2 <= s.pending_buf_size) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + strm.adler = 0; //crc32(0L, Z_NULL, 0); + s.status = BUSY_STATE; + } + } + else { + s.status = BUSY_STATE; + } + } +//#endif + + /* Flush as much pending output as possible */ + if (s.pending !== 0) { + flush_pending(strm); + if (strm.avail_out === 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s.last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && + flush !== Z_FINISH) { + return err(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s.status === FINISH_STATE && strm.avail_in !== 0) { + return err(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm.avail_in !== 0 || s.lookahead !== 0 || + (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) { + var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) : + (s.strategy === Z_RLE ? deflate_rle(s, flush) : + configuration_table[s.level].func(s, flush)); + + if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { + s.status = FINISH_STATE; + } + if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { + if (strm.avail_out === 0) { + s.last_flush = -1; + /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate === BS_BLOCK_DONE) { + if (flush === Z_PARTIAL_FLUSH) { + trees._tr_align(s); + } + else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ + + trees._tr_stored_block(s, 0, 0, false); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush === Z_FULL_FLUSH) { + /*** CLEAR_HASH(s); ***/ /* forget history */ + zero(s.head); // Fill with NIL (= 0); + + if (s.lookahead === 0) { + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + } + } + flush_pending(strm); + if (strm.avail_out === 0) { + s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + //Assert(strm->avail_out > 0, "bug2"); + //if (strm.avail_out <= 0) { throw new Error("bug2");} + + if (flush !== Z_FINISH) { return Z_OK; } + if (s.wrap <= 0) { return Z_STREAM_END; } + + /* Write the trailer */ + if (s.wrap === 2) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + put_byte(s, (strm.adler >> 16) & 0xff); + put_byte(s, (strm.adler >> 24) & 0xff); + put_byte(s, strm.total_in & 0xff); + put_byte(s, (strm.total_in >> 8) & 0xff); + put_byte(s, (strm.total_in >> 16) & 0xff); + put_byte(s, (strm.total_in >> 24) & 0xff); + } + else + { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s.wrap > 0) { s.wrap = -s.wrap; } + /* write the trailer only once! */ + return s.pending !== 0 ? Z_OK : Z_STREAM_END; +} + +function deflateEnd(strm) { + var status; + + if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { + return Z_STREAM_ERROR; + } + + status = strm.state.status; + if (status !== INIT_STATE && + status !== EXTRA_STATE && + status !== NAME_STATE && + status !== COMMENT_STATE && + status !== HCRC_STATE && + status !== BUSY_STATE && + status !== FINISH_STATE + ) { + return err(strm, Z_STREAM_ERROR); + } + + strm.state = null; + + return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK; +} + + +/* ========================================================================= + * Initializes the compression dictionary from the given byte + * sequence without producing any compressed output. + */ +function deflateSetDictionary(strm, dictionary) { + var dictLength = dictionary.length; + + var s; + var str, n; + var wrap; + var avail; + var next; + var input; + var tmpDict; + + if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { + return Z_STREAM_ERROR; + } + + s = strm.state; + wrap = s.wrap; + + if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { + return Z_STREAM_ERROR; + } + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap === 1) { + /* adler32(strm->adler, dictionary, dictLength); */ + strm.adler = adler32(strm.adler, dictionary, dictLength, 0); + } + + s.wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s.w_size) { + if (wrap === 0) { /* already empty otherwise */ + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + /* use the tail */ + // dictionary = dictionary.slice(dictLength - s.w_size); + tmpDict = new utils.Buf8(s.w_size); + utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0); + dictionary = tmpDict; + dictLength = s.w_size; + } + /* insert dictionary into window and hash */ + avail = strm.avail_in; + next = strm.next_in; + input = strm.input; + strm.avail_in = dictLength; + strm.next_in = 0; + strm.input = dictionary; + fill_window(s); + while (s.lookahead >= MIN_MATCH) { + str = s.strstart; + n = s.lookahead - (MIN_MATCH - 1); + do { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + + s.head[s.ins_h] = str; + str++; + } while (--n); + s.strstart = str; + s.lookahead = MIN_MATCH - 1; + fill_window(s); + } + s.strstart += s.lookahead; + s.block_start = s.strstart; + s.insert = s.lookahead; + s.lookahead = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + strm.next_in = next; + strm.input = input; + strm.avail_in = avail; + s.wrap = wrap; + return Z_OK; +} + + +export { deflateInit, deflateInit2, deflateReset, deflateResetKeep, deflateSetHeader, deflate, deflateEnd, deflateSetDictionary }; +export const deflateInfo = 'pako deflate (from Nodeca project)'; + +/* Not implemented +exports.deflateBound = deflateBound; +exports.deflateCopy = deflateCopy; +exports.deflateParams = deflateParams; +exports.deflatePending = deflatePending; +exports.deflatePrime = deflatePrime; +exports.deflateTune = deflateTune; +*/ diff --git a/vendor/pako/lib/zlib/gzheader.js b/vendor/pako/lib/zlib/gzheader.js new file mode 100644 index 00000000..2ec586d3 --- /dev/null +++ b/vendor/pako/lib/zlib/gzheader.js @@ -0,0 +1,35 @@ +export default function GZheader() { + /* true if compressed data believed to be text */ + this.text = 0; + /* modification time */ + this.time = 0; + /* extra flags (not used when writing a gzip file) */ + this.xflags = 0; + /* operating system */ + this.os = 0; + /* pointer to extra field or Z_NULL if none */ + this.extra = null; + /* extra field length (valid if extra != Z_NULL) */ + this.extra_len = 0; // Actually, we don't need it in JS, + // but leave for few code modifications + + // + // Setup limits is not necessary because in js we should not preallocate memory + // for inflate use constant limit in 65536 bytes + // + + /* space at extra (only when reading header) */ + // this.extra_max = 0; + /* pointer to zero-terminated file name or Z_NULL */ + this.name = ''; + /* space at name (only when reading header) */ + // this.name_max = 0; + /* pointer to zero-terminated comment or Z_NULL */ + this.comment = ''; + /* space at comment (only when reading header) */ + // this.comm_max = 0; + /* true if there was or will be a header crc */ + this.hcrc = 0; + /* true when done reading gzip header (not used when writing a gzip file) */ + this.done = false; +} diff --git a/vendor/pako/lib/zlib/inffast.js b/vendor/pako/lib/zlib/inffast.js new file mode 100644 index 00000000..889dcc7a --- /dev/null +++ b/vendor/pako/lib/zlib/inffast.js @@ -0,0 +1,324 @@ +// See state defs from inflate.js +var BAD = 30; /* got a data error -- remain here until reset */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +export default function inflate_fast(strm, start) { + var state; + var _in; /* local strm.input */ + var last; /* have enough input while in < last */ + var _out; /* local strm.output */ + var beg; /* inflate()'s initial strm.output */ + var end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + var dmax; /* maximum distance from zlib header */ +//#endif + var wsize; /* window size or zero if not using window */ + var whave; /* valid bytes in the window */ + var wnext; /* window write index */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + var s_window; /* allocated sliding window, if wsize != 0 */ + var hold; /* local strm.hold */ + var bits; /* local strm.bits */ + var lcode; /* local strm.lencode */ + var dcode; /* local strm.distcode */ + var lmask; /* mask for first level of length codes */ + var dmask; /* mask for first level of distance codes */ + var here; /* retrieved table entry */ + var op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + var len; /* match length, unused bytes */ + var dist; /* match distance */ + var from; /* where to copy match from */ + var from_source; + + + var input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + s_window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } + +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = s_window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; +}; diff --git a/vendor/pako/lib/zlib/inflate.js b/vendor/pako/lib/zlib/inflate.js new file mode 100644 index 00000000..2558bd10 --- /dev/null +++ b/vendor/pako/lib/zlib/inflate.js @@ -0,0 +1,1527 @@ +import * as utils from "../utils/common.js"; +import adler32 from "./adler32.js"; +import crc32 from "./crc32.js"; +import inflate_fast from "./inffast.js"; +import inflate_table from "./inftrees.js"; + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +/* Allowed flush values; see deflate() and inflate() below for details */ +//var Z_NO_FLUSH = 0; +//var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +//var Z_FULL_FLUSH = 3; +var Z_FINISH = 4; +var Z_BLOCK = 5; +var Z_TREES = 6; + + +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ +var Z_OK = 0; +var Z_STREAM_END = 1; +var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; +var Z_STREAM_ERROR = -2; +var Z_DATA_ERROR = -3; +var Z_MEM_ERROR = -4; +var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + +/* The deflate compression method */ +var Z_DEFLATED = 8; + + +/* STATES ====================================================================*/ +/* ===========================================================================*/ + + +var HEAD = 1; /* i: waiting for magic header */ +var FLAGS = 2; /* i: waiting for method and flags (gzip) */ +var TIME = 3; /* i: waiting for modification time (gzip) */ +var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ +var EXLEN = 5; /* i: waiting for extra length (gzip) */ +var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ +var NAME = 7; /* i: waiting for end of file name (gzip) */ +var COMMENT = 8; /* i: waiting for end of comment (gzip) */ +var HCRC = 9; /* i: waiting for header crc (gzip) */ +var DICTID = 10; /* i: waiting for dictionary check value */ +var DICT = 11; /* waiting for inflateSetDictionary() call */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ +var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ +var STORED = 14; /* i: waiting for stored size (length and complement) */ +var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ +var COPY = 16; /* i/o: waiting for input or output to copy stored block */ +var TABLE = 17; /* i: waiting for dynamic block table lengths */ +var LENLENS = 18; /* i: waiting for code length code lengths */ +var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ +var LEN_ = 20; /* i: same as LEN below, but only first time in */ +var LEN = 21; /* i: waiting for length/lit/eob code */ +var LENEXT = 22; /* i: waiting for length extra bits */ +var DIST = 23; /* i: waiting for distance code */ +var DISTEXT = 24; /* i: waiting for distance extra bits */ +var MATCH = 25; /* o: waiting for output space to copy string */ +var LIT = 26; /* o: waiting for output space to write literal */ +var CHECK = 27; /* i: waiting for 32-bit check value */ +var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ +var DONE = 29; /* finished check, done -- remain here until reset */ +var BAD = 30; /* got a data error -- remain here until reset */ +var MEM = 31; /* got an inflate() memory error -- remain here until reset */ +var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ + +/* ===========================================================================*/ + + + +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var MAX_WBITS = 15; +/* 32K LZ77 window */ +var DEF_WBITS = MAX_WBITS; + + +function zswap32(q) { + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +} + + +function InflateState() { + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib) */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ + this.work = new utils.Buf16(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} + +function inflateResetKeep(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); + state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +function inflateReset(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); + +} + +function inflateReset2(strm, windowBits) { + var wrap; + var state; + + /* get the state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 1; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +} + +function inflateInit2(strm, windowBits) { + var ret; + var state; + + if (!strm) { return Z_STREAM_ERROR; } + //strm.msg = Z_NULL; /* in case we return an error */ + + state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.window = null/*Z_NULL*/; + ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK) { + strm.state = null/*Z_NULL*/; + } + return ret; +} + +function inflateInit(strm) { + return inflateInit2(strm, DEF_WBITS); +} + + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +var virgin = true; + +var lenfix, distfix; // We have no pointers in JS, so keep tables separate + +function fixedtables(state) { + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + var sym; + + lenfix = new utils.Buf32(512); + distfix = new utils.Buf32(32); + + /* literal/length table */ + sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } + + inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); + + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } + + inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +} + + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +function updatewindow(strm, src, end, copy) { + var dist; + var state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new utils.Buf8(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + utils.arraySet(state.window, src, end - copy, dist, state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + utils.arraySet(state.window, src, end - copy, copy, 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; +} + +function inflate(strm, flush) { + var state; + var input, output; // input/output buffers + var next; /* next input INDEX */ + var put; /* next output INDEX */ + var have, left; /* available input and output */ + var hold; /* bit buffer */ + var bits; /* bits in bit buffer */ + var _in, _out; /* save starting available input and output */ + var copy; /* number of stored or match bytes to copy */ + var from; /* where to copy match bytes from */ + var from_source; + var here = 0; /* current decoding table entry */ + var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //var last; /* parent table entry */ + var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + var len; /* length to copy for repeats, bits to drop */ + var ret; /* return code */ + var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ + var opts; + + var n; // temporary var for NEED_BITS + + var order = /* permutation of code lengths */ + [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; + + + if (!strm || !strm.state || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR; + } + + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + state.flags = 0; /* expect zlib header */ + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + else if (len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } + state.dmax = 1 << len; + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if (state.flags & 0x0200) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more conveniend processing later + state.head.extra = new Array(state.head.extra_len); + } + utils.arraySet( + state.head.extra, + input, + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + copy, + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = zswap32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + utils.arraySet(output, input, next, copy, put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = { bits: state.lenbits }; + ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) { break; } + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = { bits: state.lenbits }; + ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = { bits: state.distbits }; + ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inflate_fast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' insdead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if (_out) { + strm.adler = state.check = + /*UPDATE(state.check, put - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.flags ? hold : zswap32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR; + break inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { + state.mode = MEM; + return Z_MEM_ERROR; + } + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if (state.wrap && _out) { + strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { + ret = Z_BUF_ERROR; + } + return ret; +} + +function inflateEnd(strm) { + + if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { + return Z_STREAM_ERROR; + } + + var state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK; +} + +function inflateGetHeader(strm, head) { + var state; + + /* check state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK; +} + +function inflateSetDictionary(strm, dictionary) { + var dictLength = dictionary.length; + + var state; + var dictid; + var ret; + + /* check state */ + if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +export { inflateReset, inflateReset2, inflateResetKeep, inflateInit, inflateInit2, inflate, inflateEnd, inflateGetHeader, inflateSetDictionary }; +export const inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +exports.inflateCopy = inflateCopy; +exports.inflateGetDictionary = inflateGetDictionary; +exports.inflateMark = inflateMark; +exports.inflatePrime = inflatePrime; +exports.inflateSync = inflateSync; +exports.inflateSyncPoint = inflateSyncPoint; +exports.inflateUndermine = inflateUndermine; +*/ diff --git a/vendor/pako/lib/zlib/inftrees.js b/vendor/pako/lib/zlib/inftrees.js new file mode 100644 index 00000000..78b7c9eb --- /dev/null +++ b/vendor/pako/lib/zlib/inftrees.js @@ -0,0 +1,322 @@ +import * as utils from "../utils/common.js"; + +var MAXBITS = 15; +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +var lbase = [ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +]; + +var lext = [ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 +]; + +var dbase = [ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 +]; + +var dext = [ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 +]; + +export default function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) +{ + var bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + var len = 0; /* a code's length in bits */ + var sym = 0; /* index of code symbols */ + var min = 0, max = 0; /* minimum and maximum code lengths */ + var root = 0; /* number of index bits for root table */ + var curr = 0; /* number of index bits for current table */ + var drop = 0; /* code bits to drop for sub-table */ + var left = 0; /* number of prefix codes available */ + var used = 0; /* code entries in table used */ + var huff = 0; /* Huffman code */ + var incr; /* for incrementing code, index */ + var fill; /* index for replicating entries */ + var low; /* low bits for current root entry */ + var mask; /* mask for low root bits */ + var next; /* next available space in table */ + var base = null; /* base value table to use */ + var base_index = 0; +// var shoextra; /* extra bits table to use */ + var end; /* use base and extra for symbol > end */ + var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ + var extra = null; + var extra_index = 0; + + var here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES) { + base = extra = work; /* dummy value--not used */ + end = 19; + + } else if (type === LENS) { + base = lbase; + base_index -= 257; + extra = lext; + extra_index -= 257; + end = 256; + + } else { /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here_bits = len - drop; + if (work[sym] < end) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] > end) { + here_op = extra[extra_index + work[sym]]; + here_val = base[base_index + work[sym]]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; diff --git a/vendor/pako/lib/zlib/messages.js b/vendor/pako/lib/zlib/messages.js new file mode 100644 index 00000000..f95cb704 --- /dev/null +++ b/vendor/pako/lib/zlib/messages.js @@ -0,0 +1,11 @@ +export default { + 2: 'need dictionary', /* Z_NEED_DICT 2 */ + 1: 'stream end', /* Z_STREAM_END 1 */ + 0: '', /* Z_OK 0 */ + '-1': 'file error', /* Z_ERRNO (-1) */ + '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ + '-3': 'data error', /* Z_DATA_ERROR (-3) */ + '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ + '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ + '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ +}; diff --git a/vendor/pako/lib/zlib/trees.js b/vendor/pako/lib/zlib/trees.js new file mode 100644 index 00000000..a69b8a59 --- /dev/null +++ b/vendor/pako/lib/zlib/trees.js @@ -0,0 +1,1195 @@ +import * as utils from "../utils/common.js"; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +//var Z_FILTERED = 1; +//var Z_HUFFMAN_ONLY = 2; +//var Z_RLE = 3; +var Z_FIXED = 4; +//var Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +var Z_BINARY = 0; +var Z_TEXT = 1; +//var Z_ASCII = 1; // = Z_TEXT +var Z_UNKNOWN = 2; + +/*============================================================================*/ + + +function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } + +// From zutil.h + +var STORED_BLOCK = 0; +var STATIC_TREES = 1; +var DYN_TREES = 2; +/* The three kinds of block type */ + +var MIN_MATCH = 3; +var MAX_MATCH = 258; +/* The minimum and maximum match lengths */ + +// From deflate.h +/* =========================================================================== + * Internal compression state. + */ + +var LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ + +var LITERALS = 256; +/* number of literal bytes 0..255 */ + +var L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ + +var D_CODES = 30; +/* number of distance codes */ + +var BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ + +var HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ + +var MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +var Buf_size = 16; +/* size of bit buffer in bi_buf */ + + +/* =========================================================================== + * Constants + */ + +var MAX_BL_BITS = 7; +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +var END_BLOCK = 256; +/* end of block literal code */ + +var REP_3_6 = 16; +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +var REPZ_3_10 = 17; +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +var REPZ_11_138 = 18; +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +/* eslint-disable comma-spacing,array-bracket-spacing */ +var extra_lbits = /* extra bits for each length code */ + [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; + +var extra_dbits = /* extra bits for each distance code */ + [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]; + +var extra_blbits = /* extra bits for each bit length code */ + [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]; + +var bl_order = + [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]; +/* eslint-enable comma-spacing,array-bracket-spacing */ + +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +// We pre-fill arrays with 0 to avoid uninitialized gaps + +var DIST_CODE_LEN = 512; /* see definition of array dist_code below */ + +// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1 +var static_ltree = new Array((L_CODES + 2) * 2); +zero(static_ltree); +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +var static_dtree = new Array(D_CODES * 2); +zero(static_dtree); +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +var _dist_code = new Array(DIST_CODE_LEN); +zero(_dist_code); +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +var _length_code = new Array(MAX_MATCH - MIN_MATCH + 1); +zero(_length_code); +/* length code for each normalized match length (0 == MIN_MATCH) */ + +var base_length = new Array(LENGTH_CODES); +zero(base_length); +/* First normalized length for each code (0 = MIN_MATCH) */ + +var base_dist = new Array(D_CODES); +zero(base_dist); +/* First normalized distance for each code (0 = distance of 1) */ + + +function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { + + this.static_tree = static_tree; /* static tree or NULL */ + this.extra_bits = extra_bits; /* extra bits for each code or NULL */ + this.extra_base = extra_base; /* base index for extra_bits */ + this.elems = elems; /* max number of elements in the tree */ + this.max_length = max_length; /* max bit length for the codes */ + + // show if `static_tree` has data or dummy - needed for monomorphic objects + this.has_stree = static_tree && static_tree.length; +} + + +var static_l_desc; +var static_d_desc; +var static_bl_desc; + + +function TreeDesc(dyn_tree, stat_desc) { + this.dyn_tree = dyn_tree; /* the dynamic tree */ + this.max_code = 0; /* largest code with non zero frequency */ + this.stat_desc = stat_desc; /* the corresponding static tree */ +} + + + +function d_code(dist) { + return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; +} + + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +function put_short(s, w) { +// put_byte(s, (uch)((w) & 0xff)); +// put_byte(s, (uch)((ush)(w) >> 8)); + s.pending_buf[s.pending++] = (w) & 0xff; + s.pending_buf[s.pending++] = (w >>> 8) & 0xff; +} + + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +function send_bits(s, value, length) { + if (s.bi_valid > (Buf_size - length)) { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + put_short(s, s.bi_buf); + s.bi_buf = value >> (Buf_size - s.bi_valid); + s.bi_valid += length - Buf_size; + } else { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + s.bi_valid += length; + } +} + + +function send_code(s, c, tree) { + send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); +} + + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +function bi_reverse(code, len) { + var res = 0; + do { + res |= code & 1; + code >>>= 1; + res <<= 1; + } while (--len > 0); + return res >>> 1; +} + + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +function bi_flush(s) { + if (s.bi_valid === 16) { + put_short(s, s.bi_buf); + s.bi_buf = 0; + s.bi_valid = 0; + + } else if (s.bi_valid >= 8) { + s.pending_buf[s.pending++] = s.bi_buf & 0xff; + s.bi_buf >>= 8; + s.bi_valid -= 8; + } +} + + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +function gen_bitlen(s, desc) +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + var tree = desc.dyn_tree; + var max_code = desc.max_code; + var stree = desc.stat_desc.static_tree; + var has_stree = desc.stat_desc.has_stree; + var extra = desc.stat_desc.extra_bits; + var base = desc.stat_desc.extra_base; + var max_length = desc.stat_desc.max_length; + var h; /* heap index */ + var n, m; /* iterate over the tree elements */ + var bits; /* bit length */ + var xbits; /* extra bits */ + var f; /* frequency */ + var overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) { + s.bl_count[bits] = 0; + } + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ + + for (h = s.heap_max + 1; h < HEAP_SIZE; h++) { + n = s.heap[h]; + bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; + if (bits > max_length) { + bits = max_length; + overflow++; + } + tree[n * 2 + 1]/*.Len*/ = bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) { continue; } /* not a leaf node */ + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) { + xbits = extra[n - base]; + } + f = tree[n * 2]/*.Freq*/; + s.opt_len += f * (bits + xbits); + if (has_stree) { + s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); + } + } + if (overflow === 0) { return; } + + // Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length - 1; + while (s.bl_count[bits] === 0) { bits--; } + s.bl_count[bits]--; /* move one leaf down the tree */ + s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ + s.bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits !== 0; bits--) { + n = s.bl_count[bits]; + while (n !== 0) { + m = s.heap[--h]; + if (m > max_code) { continue; } + if (tree[m * 2 + 1]/*.Len*/ !== bits) { + // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; + tree[m * 2 + 1]/*.Len*/ = bits; + } + n--; + } + } +} + + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +function gen_codes(tree, max_code, bl_count) +// ct_data *tree; /* the tree to decorate */ +// int max_code; /* largest code with non zero frequency */ +// ushf *bl_count; /* number of codes at each bit length */ +{ + var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */ + var code = 0; /* running code value */ + var bits; /* bit index */ + var n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits - 1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + //Assert (code + bl_count[MAX_BITS]-1 == (1< length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES - 1; code++) { + base_length[code] = length; + for (n = 0; n < (1 << extra_lbits[code]); n++) { + _length_code[length++] = code; + } + } + //Assert (length == 256, "tr_static_init: length != 256"); + /* Note that the length 255 (match length 258) can be represented + * in two different ways: code 284 + 5 bits or code 285, so we + * overwrite length_code[255] to use the best encoding: + */ + _length_code[length - 1] = code; + + /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ + dist = 0; + for (code = 0; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1 << extra_dbits[code]); n++) { + _dist_code[dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: dist != 256"); + dist >>= 7; /* from now on, all distances are divided by 128 */ + for (; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { + _dist_code[256 + dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) { + bl_count[bits] = 0; + } + + n = 0; + while (n <= 143) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + while (n <= 255) { + static_ltree[n * 2 + 1]/*.Len*/ = 9; + n++; + bl_count[9]++; + } + while (n <= 279) { + static_ltree[n * 2 + 1]/*.Len*/ = 7; + n++; + bl_count[7]++; + } + while (n <= 287) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes(static_ltree, L_CODES + 1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n * 2 + 1]/*.Len*/ = 5; + static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); + } + + // Now data ready and we can init static trees + static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS); + static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); + static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); + + //static_init_done = true; +} + + +/* =========================================================================== + * Initialize a new block. + */ +function init_block(s) { + var n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } + + s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; + s.opt_len = s.static_len = 0; + s.last_lit = s.matches = 0; +} + + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +function bi_windup(s) +{ + if (s.bi_valid > 8) { + put_short(s, s.bi_buf); + } else if (s.bi_valid > 0) { + //put_byte(s, (Byte)s->bi_buf); + s.pending_buf[s.pending++] = s.bi_buf; + } + s.bi_buf = 0; + s.bi_valid = 0; +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +function copy_block(s, buf, len, header) +//DeflateState *s; +//charf *buf; /* the input data */ +//unsigned len; /* its length */ +//int header; /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + + if (header) { + put_short(s, len); + put_short(s, ~len); + } +// while (len--) { +// put_byte(s, *buf++); +// } + utils.arraySet(s.pending_buf, s.window, buf, len, s.pending); + s.pending += len; +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +function smaller(tree, n, m, depth) { + var _n2 = n * 2; + var _m2 = m * 2; + return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || + (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); +} + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +function pqdownheap(s, tree, k) +// deflate_state *s; +// ct_data *tree; /* the tree to restore */ +// int k; /* node to move down */ +{ + var v = s.heap[k]; + var j = k << 1; /* left son of k */ + while (j <= s.heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s.heap_len && + smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s.heap[j], s.depth)) { break; } + + /* Exchange v with the smallest son */ + s.heap[k] = s.heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s.heap[k] = v; +} + + +// inlined manually +// var SMALLEST = 1; + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +function compress_block(s, ltree, dtree) +// deflate_state *s; +// const ct_data *ltree; /* literal tree */ +// const ct_data *dtree; /* distance tree */ +{ + var dist; /* distance of matched string */ + var lc; /* match length or unmatched char (if dist == 0) */ + var lx = 0; /* running index in l_buf */ + var code; /* the code to send */ + var extra; /* number of extra bits to send */ + + if (s.last_lit !== 0) { + do { + dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]); + lc = s.pending_buf[s.l_buf + lx]; + lx++; + + if (dist === 0) { + send_code(s, lc, ltree); /* send a literal byte */ + //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code + LITERALS + 1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra !== 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + //Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra !== 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + // "pendingBuf overflow"); + + } while (lx < s.last_lit); + } + + send_code(s, END_BLOCK, ltree); +} + + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +function build_tree(s, desc) +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + var tree = desc.dyn_tree; + var stree = desc.stat_desc.static_tree; + var has_stree = desc.stat_desc.has_stree; + var elems = desc.stat_desc.elems; + var n, m; /* iterate over heap elements */ + var max_code = -1; /* largest code with non zero frequency */ + var node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n * 2]/*.Freq*/ !== 0) { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + + } else { + tree[n * 2 + 1]/*.Len*/ = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s.heap_len < 2) { + node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); + tree[node * 2]/*.Freq*/ = 1; + s.depth[node] = 0; + s.opt_len--; + + if (has_stree) { + s.static_len -= stree[node * 2 + 1]/*.Len*/; + } + /* node is 0 or 1 so it does not have extra bits */ + } + desc.max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + //pqremove(s, tree, n); /* n = node of least frequency */ + /*** pqremove ***/ + n = s.heap[1/*SMALLEST*/]; + s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; + pqdownheap(s, tree, 1/*SMALLEST*/); + /***/ + + m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ + + s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ + s.heap[--s.heap_max] = m; + + /* Create a new node father of n and m */ + tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; + s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; + tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; + + /* and insert the new node in the heap */ + s.heap[1/*SMALLEST*/] = node++; + pqdownheap(s, tree, 1/*SMALLEST*/); + + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes(tree, max_code, s.bl_count); +} + + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +function scan_tree(s, tree, max_code) +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + var n; /* iterates over all tree elements */ + var prevlen = -1; /* last emitted length */ + var curlen; /* length of current code */ + + var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + var count = 0; /* repeat count of the current code */ + var max_count = 7; /* max repeat count */ + var min_count = 4; /* min repeat count */ + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + s.bl_tree[curlen * 2]/*.Freq*/ += count; + + } else if (curlen !== 0) { + + if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } + s.bl_tree[REP_3_6 * 2]/*.Freq*/++; + + } else if (count <= 10) { + s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; + + } else { + s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; + } + + count = 0; + prevlen = curlen; + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +} + + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +function send_tree(s, tree, max_code) +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + var n; /* iterates over all tree elements */ + var prevlen = -1; /* last emitted length */ + var curlen; /* length of current code */ + + var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + var count = 0; /* repeat count of the current code */ + var max_count = 7; /* max repeat count */ + var min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); + + } else if (curlen !== 0) { + if (curlen !== prevlen) { + send_code(s, curlen, s.bl_tree); + count--; + } + //Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s.bl_tree); + send_bits(s, count - 3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s.bl_tree); + send_bits(s, count - 3, 3); + + } else { + send_code(s, REPZ_11_138, s.bl_tree); + send_bits(s, count - 11, 7); + } + + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +} + + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +function build_bl_tree(s) { + var max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, s.dyn_ltree, s.l_desc.max_code); + scan_tree(s, s.dyn_dtree, s.d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, s.bl_desc); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { + if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { + break; + } + } + /* Update opt_len to include the bit length tree and counts */ + s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + // s->opt_len, s->static_len)); + + return max_blindex; +} + + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +function send_all_trees(s, lcodes, dcodes, blcodes) +// deflate_state *s; +// int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + var rank; /* index in bl_order */ + + //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + // "too many codes"); + //Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); + } + //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ + //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ + //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "black list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +function detect_data_type(s) { + /* black_mask is the bit mask of black-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + var black_mask = 0xf3ffc07f; + var n; + + /* Check for non-textual ("black-listed") bytes. */ + for (n = 0; n <= 31; n++, black_mask >>>= 1) { + if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { + return Z_BINARY; + } + } + + /* Check for textual ("white-listed") bytes. */ + if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || + s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + for (n = 32; n < LITERALS; n++) { + if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + } + + /* There are no "black-listed" or "white-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +} + + +var static_init_done = false; + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +function _tr_init(s) +{ + + if (!static_init_done) { + tr_static_init(); + static_init_done = true; + } + + s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); + s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); + s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); + + s.bi_buf = 0; + s.bi_valid = 0; + + /* Initialize the first block of the first file: */ + init_block(s); +} + + +/* =========================================================================== + * Send a stored block + */ +function _tr_stored_block(s, buf, stored_len, last) +//DeflateState *s; +//charf *buf; /* input block */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ + copy_block(s, buf, stored_len, true); /* with header */ +} + + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +function _tr_align(s) { + send_bits(s, STATIC_TREES << 1, 3); + send_code(s, END_BLOCK, static_ltree); + bi_flush(s); +} + + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +function _tr_flush_block(s, buf, stored_len, last) +//DeflateState *s; +//charf *buf; /* input block, or NULL if too old */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + var opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + var max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s.level > 0) { + + /* Check if the file is binary or text */ + if (s.strm.data_type === Z_UNKNOWN) { + s.strm.data_type = detect_data_type(s); + } + + /* Construct the literal and distance trees */ + build_tree(s, s.l_desc); + // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + + build_tree(s, s.d_desc); + // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s.opt_len + 3 + 7) >>> 3; + static_lenb = (s.static_len + 3 + 7) >>> 3; + + // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + // s->last_lit)); + + if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } + + } else { + // Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + + if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { + /* 4: two words for the lengths */ + + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, last); + + } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) { + + send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); + compress_block(s, static_ltree, static_dtree); + + } else { + send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); + send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); + compress_block(s, s.dyn_ltree, s.dyn_dtree); + } + // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); + } + // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + // s->compressed_len-7*last)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +function _tr_tally(s, dist, lc) +// deflate_state *s; +// unsigned dist; /* distance of matched string */ +// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + //var out_length, in_length, dcode; + + s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff; + s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff; + + s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff; + s.last_lit++; + + if (dist === 0) { + /* lc is the unmatched char */ + s.dyn_ltree[lc * 2]/*.Freq*/++; + } else { + s.matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + //Assert((ush)dist < (ush)MAX_DIST(s) && + // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++; + s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; + } + +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility + +//#ifdef TRUNCATE_BLOCK +// /* Try to guess if it is profitable to stop the current block here */ +// if ((s.last_lit & 0x1fff) === 0 && s.level > 2) { +// /* Compute an upper bound for the compressed length */ +// out_length = s.last_lit*8; +// in_length = s.strstart - s.block_start; +// +// for (dcode = 0; dcode < D_CODES; dcode++) { +// out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]); +// } +// out_length >>>= 3; +// //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", +// // s->last_lit, in_length, out_length, +// // 100L - out_length*100L/in_length)); +// if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) { +// return true; +// } +// } +//#endif + + return (s.last_lit === s.lit_bufsize - 1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +export { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align }; diff --git a/vendor/pako/lib/zlib/zstream.js b/vendor/pako/lib/zlib/zstream.js new file mode 100644 index 00000000..e7e674e7 --- /dev/null +++ b/vendor/pako/lib/zlib/zstream.js @@ -0,0 +1,24 @@ +export default function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} From 4faaf43176e33239567bcb37fbcbb5ccb9185027 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Fri, 3 Feb 2017 23:59:44 -0500 Subject: [PATCH 04/19] Use ES6 modules natively via Polyfill This commit introduces the "Browser ES Module Loader" polyfill to support developing with native ES6 modules, without any compilation step (files are passed through Babel in the browser). This should not be used in production -- a pre-compiled version passed through babel ahead of time (as produced by the `npm install` hook or `utils/use_require.js`) should be used instead. --- package.json | 1 + vnc.html | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 93f6a894..61c3ecc6 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", "babelify": "^7.3.0", + "browser-es-module-loader": "^0.4.1", "browserify": "^13.1.0", "casperjs": "^1.1.3", "chai": "^3.5.0", diff --git a/vnc.html b/vnc.html index a212a26e..64592f51 100644 --- a/vnc.html +++ b/vnc.html @@ -322,9 +322,9 @@ - - - + + + From 6cae7b58b88f2f7ba52fb32c09436e42a77e6108 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Sat, 4 Feb 2017 17:12:00 -0500 Subject: [PATCH 05/19] Allow transforming to any format This changes around `utils/use_require.js` to be able to generate any of AMD (RequireJS), CommonJS, SystemJS, or UMD modules. The three former also include support for translating `vnc.html`, producing a full "app" version of noVNC. --- package.json | 12 ++- utils/make-module-transform.js | 25 ----- utils/use_require.js | 181 ++++++++++++++++++--------------- utils/use_require_helpers.js | 40 ++++++++ 4 files changed, 148 insertions(+), 110 deletions(-) delete mode 100644 utils/make-module-transform.js create mode 100644 utils/use_require_helpers.js diff --git a/package.json b/package.json index 61c3ecc6..f69f9cea 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ }, "scripts": { "test": "PATH=$PATH:node_modules/karma/bin karma start karma.conf.js", - "prepublish": "node ./utils/use_require.js --as-require", - "build-es6": "node ./utils/use_require.js" + "prepublish": "node ./utils/use_require.js --as commonjs" }, "repository": { "type": "git", @@ -29,10 +28,14 @@ "homepage": "https://github.com/kanaka/noVNC", "devDependencies": { "ansi": "^0.3.1", + "babel-core": "^6.22.1", "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", - "babelify": "^7.3.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.22.0", + "babel-plugin-transform-es2015-modules-umd": "^6.22.0", "browser-es-module-loader": "^0.4.1", + "babelify": "^7.3.0", "browserify": "^13.1.0", "casperjs": "^1.1.3", "chai": "^3.5.0", @@ -55,7 +58,6 @@ "sinon": "^1.17.6", "sinon-chai": "^2.8.0", "spooky": "^0.2.5", - "temp": "^0.8.3", - "through2": "^2.0.1" + "temp": "^0.8.3" } } diff --git a/utils/make-module-transform.js b/utils/make-module-transform.js deleted file mode 100644 index bb48ae50..00000000 --- a/utils/make-module-transform.js +++ /dev/null @@ -1,25 +0,0 @@ -var through = require('through2'); - -var singleLineRe = /\/\* \[module\] ((.(?!\*\/))+) \*\//g; -var multiLineRe = /\/\* \[module\]\n(( * .+\n)+) \*\//g; - -var skipAsModule = /\/\* \[begin skip-as-module\] \*\/(.|\n)+\/\* \[end skip-as-module\] \*\//g; - -module.exports = function (file) { - var stream = through(function (buf, enc, next) { - var bufStr = buf.toString('utf8'); - bufStr = bufStr.replace(singleLineRe, "$1"); - bufStr = bufStr.replace(multiLineRe, function (match, mainLines) { - return mainLines.split(" * ").join(""); - }); - - bufStr = bufStr.replace(skipAsModule, ""); - - this.push(bufStr); - next(); - }); - - stream._is_make_module = true; - - return stream; -}; diff --git a/utils/use_require.js b/utils/use_require.js index e48c9e7b..43f12af1 100755 --- a/utils/use_require.js +++ b/utils/use_require.js @@ -4,118 +4,139 @@ var path = require('path'); var program = require('commander'); var fs = require('fs'); var fse = require('fs-extra'); -var browserify = require('browserify'); - -var make_modules_transform = require('./make-module-transform'); -var babelify = require("babelify"); +const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']); program - .option('-b, --browserify', 'create a browserify bundled app') - .option('--as-require', 'output files using "require" instead of ES6 import and export') + .option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`) + .option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ') + .option('--with-app', 'process app files as well as core files') .parse(process.argv); // the various important paths +var main_path = path.resolve(__dirname, '..'); var core_path = path.resolve(__dirname, '..', 'core'); var app_path = path.resolve(__dirname, '..', 'app'); +var vendor_path = path.resolve(__dirname, '..', 'vendor'); var out_dir_base = path.resolve(__dirname, '..', 'build'); var lib_dir_base = path.resolve(__dirname, '..', 'lib'); -var make_browserify = function (src_files, opts) { - // change to the root noVNC directory - process.chdir(path.resolve(__dirname, '..')); +// walkDir *recursively* walks directories trees, +// calling the callback for all normal files found. +var walkDir = function (base_path, cb) { + fs.readdir(base_path, (err, files) => { + if (err) throw err; - var b = browserify(src_files, opts); + files.map((filename) => path.join(base_path, filename)).forEach((filepath) => { + fs.lstat(filepath, (err, stats) => { + if (err) throw err; - // register the transforms - b.transform(make_modules_transform); - b.transform(babelify, - { plugins: ["add-module-exports", "transform-es2015-modules-commonjs"] }); - - return b; -}; - -var make_full_app = function () { - // make sure the output directory exists - fse.ensureDir(out_dir_base); - - // actually bundle the files into a browserified bundled - var ui_file = path.join(app_path, 'ui.js'); - var b = make_browserify(ui_file, {}); - var app_file = path.join(out_dir_base, 'app.js'); - b.bundle().pipe(fs.createWriteStream(app_file)); - - // copy over app-related resources (images, styles, etc) - var src_dir_app = path.join(__dirname, '..', 'app'); - fs.readdir(src_dir_app, function (err, files) { - if (err) { throw err; } - - files.forEach(function (src_file) { - var src_file_path = path.resolve(src_dir_app, src_file); - var out_file_path = path.resolve(out_dir_base, src_file); - var ext = path.extname(src_file); - if (ext === '.js' || ext === '.html') return; - fse.copy(src_file_path, out_file_path, function (err) { - if (err) { throw err; } - console.log("Copied file(s) from " + src_file_path + " to " + out_file_path); + if (stats.isSymbolicLink()) return; + if (stats.isFile()) cb(filepath); + if (stats.isDirectory()) walkDir(filepath, cb); }); }); }); +}; +var transform_html = function (new_script) { // write out the modified vnc.html file that works with the bundle var src_html_path = path.resolve(__dirname, '..', 'vnc.html'); var out_html_path = path.resolve(out_dir_base, 'vnc.html'); - fs.readFile(src_html_path, function (err, contents_raw) { + fs.readFile(src_html_path, (err, contents_raw) => { if (err) { throw err; } var contents = contents_raw.toString(); - contents = contents.replace(/="app\//g, '="'); var start_marker = '\n'; var end_marker = ''; var start_ind = contents.indexOf(start_marker) + start_marker.length; var end_ind = contents.indexOf(end_marker, start_ind); - contents = contents.slice(0, start_ind) + '\n' + contents.slice(end_ind); + contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind); + console.log(`Writing ${out_html_path}`); fs.writeFile(out_html_path, contents, function (err) { if (err) { throw err; } - console.log("Wrote " + out_html_path); }); }); -}; - -var make_lib_files = function (use_require) { - // make sure the output directory exists - fse.ensureDir(lib_dir_base); - - var through = require('through2'); - - var deps = {}; - var rfb_file = path.join(core_path, 'rfb.js'); - var b = make_browserify(rfb_file, {}); - b.on('transform', function (tr, file) { - if (tr._is_make_module) { - var new_path = path.join(lib_dir_base, path.relative(core_path, file)); - fse.ensureDir(path.dirname(new_path)); - console.log("Writing " + new_path) - var fileStream = fs.createWriteStream(new_path); - - if (use_require) { - var babelificate = babelify(file, - { plugins: ["add-module-exports", "transform-es2015-modules-commonjs"] }); - tr.pipe(babelificate); - tr = babelificate; - } - tr.pipe(fileStream); - } - }); - - b.bundle(); -}; - -if (program.browserify) { - make_full_app(); -} else { - make_lib_files(program.asRequire); } + +var make_lib_files = function (import_format, source_maps, with_app_dir) { + if (!import_format) { + throw new Error("you must specify an import format to generate compiled noVNC libraries"); + } else if (!SUPPORTED_FORMATS.has(import_format)) { + throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`); + } + + // NB: we need to make a copy of babel_opts, since babel sets some defaults on it + const babel_opts = () => ({ + plugins: [`transform-es2015-modules-${import_format}`], + ast: false, + sourceMaps: source_maps, + }); + const babel = require('babel-core'); + + var in_path; + if (with_app_dir) { + var out_path_base = out_dir_base; + in_path = main_path; + } else { + var out_path_base = lib_dir_base; + } + + fse.ensureDirSync(out_path_base); + + const helpers = require('./use_require_helpers'); + const helper = helpers[import_format]; + + var handleDir = (js_only, in_path_base, filename) => { + const out_path = path.join(out_path_base, path.relative(in_path_base, filename)); + if(path.extname(filename) !== '.js') { + if (!js_only) { + console.log(`Writing ${out_path}`); + fse.copy(filename, out_path, (err) => { if (err) throw err; }); + } + return; // skip non-javascript files + } + + fse.ensureDir(path.dirname(out_path), () => { + const opts = babel_opts(); + if (helper && helpers.optionsOverride) { + helper.optionsOverride(opts); + } + babel.transformFile(filename, babel_opts(), (err, res) => { + console.log(`Writing ${out_path}`); + if (err) throw err; + var {code, map, ast} = res; + if (source_maps === true) { + // append URL for external source map + code += `\n//# sourceMappingURL=${path.basename(out_path)}.map\n`; + } + fs.writeFile(out_path, code, (err) => { if (err) throw err; }); + if (source_maps === true || source_maps === 'both') { + console.log(` and ${out_path}.map`); + fs.writeFile(`${out_path}.map`, JSON.stringify(map), (err) => { if (err) throw err; }); + } + }); + }); + }; + + walkDir(core_path, handleDir.bind(null, true, in_path || core_path)); + walkDir(vendor_path, handleDir.bind(null, true, in_path || main_path)); + + if (with_app_dir) { + walkDir(app_path, handleDir.bind(null, false, in_path || app_path)); + + const out_app_path = path.join(out_path_base, 'app.js'); + if (helper && helper.appWriter) { + console.log(`Writing ${out_app_path}`); + let out_script = helper.appWriter(out_path_base, out_app_path); + transform_html(out_script); + } else { + console.error(`Unable to generate app for the ${import_format} format!`); + } + } +}; + +make_lib_files(program.as, program.withSourceMaps, program.withApp); diff --git a/utils/use_require_helpers.js b/utils/use_require_helpers.js new file mode 100644 index 00000000..b8ac46e6 --- /dev/null +++ b/utils/use_require_helpers.js @@ -0,0 +1,40 @@ +// writes helpers require for vnc.html (they should output app.js) +var fs = require('fs'); +var fse = require('fs-extra'); +var path = require('path'); + +module.exports = { + 'amd': { + appWriter: (base_out_path, out_path) => { + // setup for requirejs + fs.writeFile(out_path, 'requirejs(["app/ui"], function (ui) {});', (err) => { if (err) throw err; }); + console.log(`Please place RequireJS in ${path.join(base_out_path, 'require.js')}`); + return ``; + }, + }, + 'commonjs': { + optionsOverride: (opts) => { + // CommonJS supports properly shifting the default export to work as normal + opts.plugins.unshift("add-module-exports"); + }, + appWriter: (base_out_path, out_path) => { + var browserify = require('browserify'); + var b = browserify(path.join(base_out_path, 'app/ui.js'), {}); + b.bundle().pipe(fs.createWriteStream(out_path)); + return ``; + }, + }, + 'systemjs': { + appWriter: (base_out_path, out_path) => { + fs.writeFile(out_path, 'SystemJS.import("./app/ui.js");', (err) => { if (err) throw err; }); + console.log(`Please place SystemJS in ${path.join(base_out_path, 'system-production.js')}`); + return `\n`; + }, + }, + 'umd': { + optionsOverride: (opts) => { + // umd supports properly shifting the default export to work as normal + opts.plugins.unshift("add-module-exports"); + }, + }, +} From bc35b1d27e233e6bb713bcfe672d0a690981c64b Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Sat, 4 Feb 2017 17:16:05 -0500 Subject: [PATCH 06/19] Remove WebUtil.dirObj `WebUtil.dirObj` wasn't being used anywhere, so this removes it. --- app/webutil.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/app/webutil.js b/app/webutil.js index 6afd015f..e77c5e20 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -33,35 +33,6 @@ WebUtil.init_logging = function (level) { Util.init_logging(); }; - -WebUtil.dirObj = function (obj, depth, parent) { - "use strict"; - if (! depth) { depth = 2; } - if (! parent) { parent = ""; } - - // Print the properties of the passed-in object - var msg = ""; - for (var i in obj) { - if ((depth > 1) && (typeof obj[i] === "object")) { - // Recurse attributes that are objects - msg += WebUtil.dirObj(obj[i], depth - 1, parent + "." + i); - } else { - //val = new String(obj[i]).replace("\n", " "); - var val = ""; - if (typeof(obj[i]) === "undefined") { - val = "undefined"; - } else { - val = obj[i].toString().replace("\n", " "); - } - if (val.length > 30) { - val = val.substr(0, 30) + "..."; - } - msg += parent + "." + i + ": " + val + "\n"; - } - } - return msg; -}; - // Read a query string variable WebUtil.getQueryVar = function (name, defVal) { "use strict"; From 2a7c6d20ab7bb3684e6450249348337b34933df6 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 14 Feb 2017 19:38:54 -0500 Subject: [PATCH 07/19] Load translations over XHR This commit causes translations to be loaded over XHR instead of the import system. --- app/locale/{de.js => de.json} | 13 +++---------- app/locale/{el.js => el.json} | 18 +++++++----------- app/locale/{nl.js => nl.json} | 13 +++---------- app/locale/{sv.js => sv.json} | 13 +++---------- app/ui.js | 24 +++++++++++++++--------- app/webutil.js | 34 ++++++++++++++++++++++++++++++++++ po/Makefile | 6 +++--- po/po2js | 25 ++++++------------------- 8 files changed, 74 insertions(+), 72 deletions(-) rename app/locale/{de.js => de.json} (77%) rename app/locale/{el.js => el.json} (95%) rename app/locale/{nl.js => nl.json} (83%) rename app/locale/{sv.js => sv.json} (93%) diff --git a/app/locale/de.js b/app/locale/de.json similarity index 77% rename from app/locale/de.js rename to app/locale/de.json index 6819c486..b8ef1623 100644 --- a/app/locale/de.js +++ b/app/locale/de.json @@ -1,11 +1,4 @@ -/* - * Translations for de - * - * This file was autotomatically generated from de.po - * DO NOT EDIT! - */ - -Language = { +{ "Connecting...": "Verbunden...", "Connected (encrypted) to ": "Verbunden mit (verschlüsselt) ", "Connected (unencrypted) to ": "Verbunden mit (unverschlüsselt) ", @@ -14,5 +7,5 @@ Language = { "Must set host and port": "Richten Sie Host und Port ein", "Password is required": "Passwort ist erforderlich", "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt", - "Disconnect timeout": "Timeout beim trennen", -}; + "Disconnect timeout": "Timeout beim trennen" +} diff --git a/app/locale/el.js b/app/locale/el.json similarity index 95% rename from app/locale/el.js rename to app/locale/el.json index 3ce6c389..1bf1f845 100644 --- a/app/locale/el.js +++ b/app/locale/el.json @@ -1,11 +1,4 @@ -/* - * Translations for el - * - * This file was autotomatically generated from el.po - * DO NOT EDIT! - */ - -Language = { +{ "Connecting...": "Συνδέεται...", "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ", "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ", @@ -15,7 +8,7 @@ Language = { "Password is required": "Απαιτείται ο κωδικός πρόσβασης", "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης σε πλήρη οθόνη στον IE", "Disconnect timeout": "Παρέλευση χρονικού ορίου αποσύνδεσης", - "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα", + "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", "viewport drag": "σύρσιμο θεατού πεδίου", @@ -65,10 +58,13 @@ Language = { "default": "προεπιλεγμένο", "Logging:": "Καταγραφή:", "Apply": "Εφαρμογή", + "Connect": "Σύνδεση", + "Disconnect": "Αποσύνδεση", + "Connection": "Σύνδεση", "Host:": "Όνομα διακομιστή:", "Port:": "Πόρτα διακομιστή:", "Password:": "Κωδικός Πρόσβασης:", "Token:": "Διακριτικό:", "Send Password": "Αποστολή Κωδικού Πρόσβασης", - "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas", -}; + "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" +} diff --git a/app/locale/nl.js b/app/locale/nl.json similarity index 83% rename from app/locale/nl.js rename to app/locale/nl.json index cacab302..74e31ec3 100644 --- a/app/locale/nl.js +++ b/app/locale/nl.json @@ -1,11 +1,4 @@ -/* - * Translations for nl - * - * This file was autotomatically generated from nl.po - * DO NOT EDIT! - */ - -Language = { +{ "Connecting...": "Verbinden...", "Connected (encrypted) to ": "Verbonden (versleuteld) met ", "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ", @@ -14,5 +7,5 @@ Language = { "Must set host and port": "Host en poort moeten worden ingesteld", "Password is required": "Wachtwoord is vereist", "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-modus in IE niet worden ondersteund", - "Disconnect timeout": "Timeout tijdens verbreken van verbinding", -}; + "Disconnect timeout": "Timeout tijdens verbreken van verbinding" +} diff --git a/app/locale/sv.js b/app/locale/sv.json similarity index 93% rename from app/locale/sv.js rename to app/locale/sv.json index b7f4c1fb..c6469564 100644 --- a/app/locale/sv.js +++ b/app/locale/sv.json @@ -1,11 +1,4 @@ -/* - * Translations for sv - * - * This file was autotomatically generated from sv.po - * DO NOT EDIT! - */ - -Language = { +{ "Connecting...": "Ansluter...", "Connected (encrypted) to ": "Ansluten (krypterat) till ", "Connected (unencrypted) to ": "Ansluten (okrypterat) till ", @@ -73,5 +66,5 @@ Language = { "Password:": "Lösenord:", "Token:": "Token:", "Send Password": "Skicka Lösenord", - "Canvas not supported.": "Canvas stöds ej", -}; + "Canvas not supported.": "Canvas stöds ej" +} diff --git a/app/ui.js b/app/ui.js index 8009c382..ce1d9c5c 100644 --- a/app/ui.js +++ b/app/ui.js @@ -63,14 +63,6 @@ var UI; return false; }); - // Set up translations - var LINGUAS = ["de", "el", "nl", "sv"]; - Util.Localisation.setup(LINGUAS); - if (Util.Localisation.language !== "en") { - WebUtil.load_scripts( - {'app': ["locale/" + Util.Localisation.language + ".js"]}); - } - var _ = Util.Localisation.get; UI = { @@ -1743,7 +1735,21 @@ var UI; */ }; - UI.load(); + // Set up translations + var LINGUAS = ["de", "el", "nl", "sv"]; + Util.Localisation.setup(LINGUAS); + if (Util.Localisation.language !== "en" && Util.Localisation.dictionary === undefined) { + WebUtil.fetchJSON('app/locale/' + Util.Localisation.language + '.json', function (translations) { + Util.Localisation.dictionary = translations; + + // wait for translations to load before loading the UI + UI.load(); + }, function (err) { + throw err; + }); + } else { + UI.load(); + } })(); export default UI; diff --git a/app/webutil.js b/app/webutil.js index e77c5e20..ca1f21bc 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -277,4 +277,38 @@ WebUtil.load_scripts = function (files_by_dir) { } }; +// sadly, we can't use the Fetch API until we decide to drop +// IE11 support or polyfill promises and fetch in IE11. +// resolve will receive an object on success, while reject +// will receive either an event or an error on failure. +WebUtil.fetchJSON = function (path, resolve, reject) { + // NB: IE11 doesn't support JSON as a responseType + const req = new XMLHttpRequest(); + req.open('GET', path); + + req.onload = function () { + if (req.status === 200) { + try { + var resObj = JSON.parse(req.responseText); + } catch (err) { + reject(err); + return; + } + resolve(resObj); + } else { + reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status)); + } + }; + + req.onerror = function (evt) { + reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message)); + }; + + req.ontimeout = function (evt) { + reject(new Error("XHR timed out while trying to load '" + path + "'")); + }; + + req.send(); +}; + export default WebUtil; diff --git a/po/Makefile b/po/Makefile index b64b7638..8c6000ba 100644 --- a/po/Makefile +++ b/po/Makefile @@ -6,14 +6,14 @@ LINGUAS := de el nl sv VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4) POFILES := $(addsuffix .po,$(LINGUAS)) -JSFILES := $(addprefix ../app/locale/,$(addsuffix .js,$(LINGUAS))) +JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS))) update-po: $(POFILES) -update-js: $(JSFILES) +update-js: $(JSONFILES) %.po: noVNC.pot msgmerge --update --lang=$* $@ $< -../app/locale/%.js: %.po +../app/locale/%.json: %.po ./po2js $< $@ update-pot: diff --git a/po/po2js b/po/po2js index 972f0b2c..15304c9c 100755 --- a/po/po2js +++ b/po/po2js @@ -32,25 +32,12 @@ if (opt.argv.length != 2) { var data = po2json.parseFileSync(opt.argv[0]); -var output = -"/*\n" + -" * Translations for " + data[""]["language"] + "\n" + -" *\n" + -" * This file was autotomatically generated from " + opt.argv[0] + "\n" + -" * DO NOT EDIT!\n" + -" */\n" + -"\n" + -"Language = {\n"; +var bodyPart = Object.keys(data).filter((msgid) => msgid !== "").map((msgid) => { + if (msgid === "") return; + var msgstr = data[msgid][1]; + return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); +}).join(",\n"); -for (msgid in data) { - if (msgid === "") - continue; - - msgstr = data[msgid][1]; - output += " " + JSON.stringify(msgid) + ": " + - JSON.stringify(msgstr) + ",\n"; -} - -output += "};\n"; +var output = "{\n" + bodyPart + "\n}"; fs.writeFileSync(opt.argv[1], output); From 6e744119f81d8dcef496fc8de539481870321819 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Sat, 4 Feb 2017 17:40:18 -0500 Subject: [PATCH 08/19] Remove WebUtil.load_scripts The only remaining user of WebUtil.load_scripts was for loading localisation. Instead, we now load the localization information over XHR as a JSON blob. --- app/webutil.js | 69 -------------------------------------------------- core/util.js | 7 +++-- package.json | 3 ++- 3 files changed, 7 insertions(+), 72 deletions(-) diff --git a/app/webutil.js b/app/webutil.js index ca1f21bc..2ce1b2d3 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -208,75 +208,6 @@ WebUtil.injectParamIfMissing = function (path, param, value) { } }; -// Dynamically load scripts without using document.write() -// Reference: http://unixpapa.com/js/dyna.html -// -// Handles the case where load_scripts is invoked from a script that -// itself is loaded via load_scripts. Once all scripts are loaded the -// window.onscriptsloaded handler is called (if set). -WebUtil.get_include_uri = function (root_dir) { - return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI + root_dir + '/' : root_dir + '/'; -}; -WebUtil._loading_scripts = []; -WebUtil._pending_scripts = []; -WebUtil.load_scripts = function (files_by_dir) { - "use strict"; - var head = document.getElementsByTagName('head')[0], script, - ls = WebUtil._loading_scripts, ps = WebUtil._pending_scripts; - - var loadFunc = function (e) { - while (ls.length > 0 && (ls[0].readyState === 'loaded' || - ls[0].readyState === 'complete')) { - // For IE, append the script to trigger execution - var s = ls.shift(); - //console.log("loaded script: " + s.src); - head.appendChild(s); - } - if (!this.readyState || - (Util.Engine.presto && this.readyState === 'loaded') || - this.readyState === 'complete') { - if (ps.indexOf(this) >= 0) { - this.onload = this.onreadystatechange = null; - //console.log("completed script: " + this.src); - ps.splice(ps.indexOf(this), 1); - - // Call window.onscriptsload after last script loads - if (ps.length === 0 && window.onscriptsload) { - window.onscriptsload(); - } - } - } - }; - - var root_dirs = Object.keys(files_by_dir); - - for (var d = 0; d < root_dirs.length; d++) { - var root_dir = root_dirs[d]; - var files = files_by_dir[root_dir]; - - for (var f = 0; f < files.length; f++) { - script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = WebUtil.get_include_uri(root_dir) + files[f]; - //console.log("loading script: " + script.src); - script.onload = script.onreadystatechange = loadFunc; - // In-order script execution tricks - if (Util.Engine.trident) { - // For IE wait until readyState is 'loaded' before - // appending it which will trigger execution - // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order - ls.push(script); - } else { - // For webkit and firefox set async=false and append now - // https://developer.mozilla.org/en-US/docs/HTML/Element/script - script.async = false; - head.appendChild(script); - } - ps.push(script); - } - } -}; - // sadly, we can't use the Fetch API until we decide to drop // IE11 support or polyfill promises and fetch in IE11. // resolve will receive an object on success, while reject diff --git a/core/util.js b/core/util.js index 9f048914..08d7d78e 100644 --- a/core/util.js +++ b/core/util.js @@ -333,6 +333,9 @@ Util.Localisation = { // Currently configured language language: 'en', + // Current dictionary of translations + dictionary: undefined, + // Configure suitable language based on user preferences setup: function (supportedLanguages) { var userLanguages; @@ -397,8 +400,8 @@ Util.Localisation = { // Retrieve localised text get: function (id) { - if (typeof Language !== 'undefined' && Language[id]) { - return Language[id]; + if (typeof Util.Localisation.dictionary !== 'undefined' && Util.Localisation.dictionary[id]) { + return Util.Localisation.dictionary[id]; } else { return id; } diff --git a/package.json b/package.json index f69f9cea..d51f07de 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,13 @@ "ansi": "^0.3.1", "babel-core": "^6.22.1", "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-es2015-modules-amd": "^6.22.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", "babel-plugin-transform-es2015-modules-systemjs": "^6.22.0", "babel-plugin-transform-es2015-modules-umd": "^6.22.0", - "browser-es-module-loader": "^0.4.1", "babelify": "^7.3.0", + "browser-es-module-loader": "^0.4.1", "browserify": "^13.1.0", "casperjs": "^1.1.3", "chai": "^3.5.0", From 6d6f0db0da5a46e530fc891136224d58771c00c6 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Sat, 4 Feb 2017 21:26:00 -0500 Subject: [PATCH 09/19] Refactor ES6 module structure/split up Util This commit restructures many of the ES6 modules, splitting them up to actual export multiple functions instead of a single object. It also splits up Util into multiple sub-modules, to make it easier to maintain. Finally, localisation is renamed to localization. --- .gitignore | 2 + LICENSE.txt | 17 +- app/ui.js | 2813 ++++++++++++------------- app/webutil.js | 68 +- core/base64.js | 10 +- core/display.js | 1428 +++++++------ core/inflator.js | 1 - core/input/devices.js | 734 ++++--- core/input/keysym.js | 4 +- core/input/keysymdef.js | 31 +- core/input/util.js | 528 +++-- core/input/xtscancodes.js | 4 +- core/rfb.js | 4109 ++++++++++++++++++------------------- core/util.js | 624 ------ core/util/browsers.js | 120 ++ core/util/events.js | 161 ++ core/util/localization.js | 170 ++ core/util/logging.js | 53 + core/util/properties.js | 138 ++ core/util/strings.js | 15 + core/websock.js | 614 +++--- 21 files changed, 5806 insertions(+), 5838 deletions(-) delete mode 100644 core/util.js create mode 100644 core/util/browsers.js create mode 100644 core/util/events.js create mode 100644 core/util/localization.js create mode 100644 core/util/logging.js create mode 100644 core/util/properties.js create mode 100644 core/util/strings.js diff --git a/.gitignore b/.gitignore index 921551cc..e9c84876 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ utils/websockify /build /lib recordings +*.swp +*~ diff --git a/LICENSE.txt b/LICENSE.txt index 39cbe3f2..460060a0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -5,20 +5,9 @@ Public License 2.0). The noVNC core library is composed of the Javascript code necessary for full noVNC operation. This includes (but is not limited to): - core/base64.js - core/des.js - core/display.js - core/input/devices.js - core/input/keysym.js - core/logo.js - core/playback.js - core/rfb.js - app/ui.js - core/util.js - core/websock.js - app/webutil.js - core/input/xtscancodes.js - core/inflator.js + core/**/*.js + app/*.js + test/playback.js The HTML, CSS, font and images files that included with the noVNC source distibution (or repository) are not considered part of the diff --git a/app/ui.js b/app/ui.js index ce1d9c5c..c53ddf70 100644 --- a/app/ui.js +++ b/app/ui.js @@ -11,443 +11,446 @@ /* jslint white: false, browser: true */ /* global window, document.getElementById, Util, WebUtil, RFB, Display */ -import Util from "../core/util.js"; +import * as Log from '../core/util/logging.js'; +import _, { l10n } from '../core/util/localization.js' +import { isTouchDevice, browserSupportsCursorURIs as cursorURIsSupported } from '../core/util/browsers.js'; +import { setCapture, getPointerEvent } from '../core/util/events.js'; import KeyTable from "../core/input/keysym.js"; import keysyms from "../core/input/keysymdef.js"; import RFB from "../core/rfb.js"; import Display from "../core/display.js"; -import WebUtil from "./webutil.js"; +import * as WebUtil from "./webutil.js"; -var UI; +// Fallback for all uncought errors +window.addEventListener('error', function(event) { + try { + var msg, div, text; -(function () { - "use strict"; + msg = document.getElementById('noVNC_fallback_errormsg'); - // Fallback for all uncought errors - window.addEventListener('error', function(event) { - try { - var msg, div, text; - - msg = document.getElementById('noVNC_fallback_errormsg'); - - // Only show the initial error - if (msg.hasChildNodes()) { - return false; - } - - div = document.createElement("div"); - div.appendChild(document.createTextNode(event.message)); - msg.appendChild(div); - - div = document.createElement("div"); - div.className = 'noVNC_location'; - text = event.filename + ":" + event.lineno + ":" + event.colno; - div.appendChild(document.createTextNode(text)); - msg.appendChild(div); - - if ((event.error !== undefined) && - (event.error.stack !== undefined)) { - div = document.createElement("div"); - div.className = 'noVNC_stack'; - div.appendChild(document.createTextNode(event.error.stack)); - msg.appendChild(div); - } - - document.getElementById('noVNC_fallback_error') - .classList.add("noVNC_open"); - } catch (exc) { - document.write("noVNC encountered an error."); + // Only show the initial error + if (msg.hasChildNodes()) { + return false; } - // Don't return true since this would prevent the error - // from being printed to the browser console. - return false; - }); - var _ = Util.Localisation.get; + div = document.createElement("div"); + div.appendChild(document.createTextNode(event.message)); + msg.appendChild(div); - UI = { + div = document.createElement("div"); + div.className = 'noVNC_location'; + text = event.filename + ":" + event.lineno + ":" + event.colno; + div.appendChild(document.createTextNode(text)); + msg.appendChild(div); - connected: false, - desktopName: "", + if ((event.error !== undefined) && + (event.error.stack !== undefined)) { + div = document.createElement("div"); + div.className = 'noVNC_stack'; + div.appendChild(document.createTextNode(event.error.stack)); + msg.appendChild(div); + } - resizeTimeout: null, - statusTimeout: null, - hideKeyboardTimeout: null, - idleControlbarTimeout: null, - closeControlbarTimeout: null, + document.getElementById('noVNC_fallback_error') + .classList.add("noVNC_open"); + } catch (exc) { + document.write("noVNC encountered an error."); + } + // Don't return true since this would prevent the error + // from being printed to the browser console. + return false; +}); - controlbarGrabbed: false, - controlbarDrag: false, - controlbarMouseDownClientY: 0, - controlbarMouseDownOffsetY: 0, +const UI = { - isSafari: false, - rememberedClipSetting: null, - lastKeyboardinput: null, - defaultKeyboardinputLen: 100, + connected: false, + desktopName: "", - inhibit_reconnect: true, - reconnect_callback: null, - reconnect_password: null, + resizeTimeout: null, + statusTimeout: null, + hideKeyboardTimeout: null, + idleControlbarTimeout: null, + closeControlbarTimeout: null, - // Setup rfb object, load settings from browser storage, then call - // UI.init to setup the UI/menus - load: function(callback) { - WebUtil.initSettings(UI.start, callback); - }, + controlbarGrabbed: false, + controlbarDrag: false, + controlbarMouseDownClientY: 0, + controlbarMouseDownOffsetY: 0, - // Render default UI and initialize settings menu - start: function(callback) { + isSafari: false, + rememberedClipSetting: null, + lastKeyboardinput: null, + defaultKeyboardinputLen: 100, - // Setup global variables first - UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 && - navigator.userAgent.indexOf('Chrome') === -1); + inhibit_reconnect: true, + reconnect_callback: null, + reconnect_password: null, - UI.initSettings(); + prime: function(callback) { + if (document.readyState === "interactive" || document.readyState === "complete") { + UI.load(callback); + } else { + document.addEventListener('DOMContentLoaded', UI.load.bind(UI, callback)); + } + }, - // Translate the DOM - Util.Localisation.translateDOM(); + // Setup rfb object, load settings from browser storage, then call + // UI.init to setup the UI/menus + load: function(callback) { + WebUtil.initSettings(UI.start, callback); + }, - // Adapt the interface for touch screen devices - if (Util.isTouchDevice) { - document.documentElement.classList.add("noVNC_touch"); - // Remove the address bar - setTimeout(function() { window.scrollTo(0, 1); }, 100); + // Render default UI and initialize settings menu + start: function(callback) { + + // Setup global variables first + UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 && + navigator.userAgent.indexOf('Chrome') === -1); + + UI.initSettings(); + + // Translate the DOM + l10n.translateDOM(); + + // Adapt the interface for touch screen devices + if (isTouchDevice) { + document.documentElement.classList.add("noVNC_touch"); + // Remove the address bar + setTimeout(function() { window.scrollTo(0, 1); }, 100); + } + + // Restore control bar position + if (WebUtil.readSetting('controlbar_pos') === 'right') { + UI.toggleControlbarSide(); + } + + UI.initFullscreen(); + + // Setup event handlers + UI.addResizeHandlers(); + UI.addControlbarHandlers(); + UI.addTouchSpecificHandlers(); + UI.addExtraKeysHandlers(); + UI.addXvpHandlers(); + UI.addConnectionControlHandlers(); + UI.addClipboardHandlers(); + UI.addSettingsHandlers(); + document.getElementById("noVNC_status") + .addEventListener('click', UI.hideStatus); + + UI.openControlbar(); + + // Show the connect panel on first load unless autoconnecting + if (!autoconnect) { + UI.openConnectPanel(); + } + + UI.updateViewClip(); + + UI.updateVisualState(); + + document.getElementById('noVNC_setting_host').focus(); + + var autoconnect = WebUtil.getConfigVar('autoconnect', false); + if (autoconnect === 'true' || autoconnect == '1') { + autoconnect = true; + UI.connect(); + } else { + autoconnect = false; + } + + if (typeof callback === "function") { + callback(UI.rfb); + } + }, + + initFullscreen: function() { + // Only show the button if fullscreen is properly supported + // * Safari doesn't support alphanumerical input while in fullscreen + if (!UI.isSafari && + (document.documentElement.requestFullscreen || + document.documentElement.mozRequestFullScreen || + document.documentElement.webkitRequestFullscreen || + document.body.msRequestFullscreen)) { + document.getElementById('noVNC_fullscreen_button') + .classList.remove("noVNC_hidden"); + UI.addFullscreenHandlers(); + } + }, + + initSettings: function() { + var i; + + // Logging selection dropdown + var llevels = ['error', 'warn', 'info', 'debug']; + for (i = 0; i < llevels.length; i += 1) { + UI.addOption(document.getElementById('noVNC_setting_logging'),llevels[i], llevels[i]); + } + + // Settings with immediate effects + UI.initSetting('logging', 'warn'); + UI.updateLogging(); + + // if port == 80 (or 443) then it won't be present and should be + // set manually + var port = window.location.port; + if (!port) { + if (window.location.protocol.substring(0,5) == 'https') { + port = 443; } - - // Restore control bar position - if (WebUtil.readSetting('controlbar_pos') === 'right') { - UI.toggleControlbarSide(); + else if (window.location.protocol.substring(0,4) == 'http') { + port = 80; } + } - UI.initFullscreen(); + /* Populate the controls if defaults are provided in the URL */ + UI.initSetting('host', window.location.hostname); + UI.initSetting('port', port); + UI.initSetting('encrypt', (window.location.protocol === "https:")); + UI.initSetting('true_color', true); + UI.initSetting('cursor', !isTouchDevice); + UI.initSetting('clip', false); + UI.initSetting('resize', 'off'); + UI.initSetting('shared', true); + UI.initSetting('view_only', false); + UI.initSetting('path', 'websockify'); + UI.initSetting('repeaterID', ''); + UI.initSetting('reconnect', false); + UI.initSetting('reconnect_delay', 5000); - // Setup event handlers - UI.addResizeHandlers(); - UI.addControlbarHandlers(); - UI.addTouchSpecificHandlers(); - UI.addExtraKeysHandlers(); - UI.addXvpHandlers(); - UI.addConnectionControlHandlers(); - UI.addClipboardHandlers(); - UI.addSettingsHandlers(); - document.getElementById("noVNC_status") - .addEventListener('click', UI.hideStatus); - - UI.openControlbar(); - - // Show the connect panel on first load unless autoconnecting - if (!autoconnect) { - UI.openConnectPanel(); - } - - UI.updateViewClip(); - - UI.updateVisualState(); - - document.getElementById('noVNC_setting_host').focus(); - - var autoconnect = WebUtil.getConfigVar('autoconnect', false); - if (autoconnect === 'true' || autoconnect == '1') { - autoconnect = true; - UI.connect(); + UI.setupSettingLabels(); + }, + // Adds a link to the label elements on the corresponding input elements + setupSettingLabels: function() { + var labels = document.getElementsByTagName('LABEL'); + for (var i = 0; i < labels.length; i++) { + var htmlFor = labels[i].htmlFor; + if (htmlFor != '') { + var elem = document.getElementById(htmlFor); + if (elem) elem.label = labels[i]; } else { - autoconnect = false; - } - - if (typeof callback === "function") { - callback(UI.rfb); - } - }, - - initFullscreen: function() { - // Only show the button if fullscreen is properly supported - // * Safari doesn't support alphanumerical input while in fullscreen - if (!UI.isSafari && - (document.documentElement.requestFullscreen || - document.documentElement.mozRequestFullScreen || - document.documentElement.webkitRequestFullscreen || - document.body.msRequestFullscreen)) { - document.getElementById('noVNC_fullscreen_button') - .classList.remove("noVNC_hidden"); - UI.addFullscreenHandlers(); - } - }, - - initSettings: function() { - var i; - - // Logging selection dropdown - var llevels = ['error', 'warn', 'info', 'debug']; - for (i = 0; i < llevels.length; i += 1) { - UI.addOption(document.getElementById('noVNC_setting_logging'),llevels[i], llevels[i]); - } - - // Settings with immediate effects - UI.initSetting('logging', 'warn'); - UI.updateLogging(); - - // if port == 80 (or 443) then it won't be present and should be - // set manually - var port = window.location.port; - if (!port) { - if (window.location.protocol.substring(0,5) == 'https') { - port = 443; - } - else if (window.location.protocol.substring(0,4) == 'http') { - port = 80; - } - } - - /* Populate the controls if defaults are provided in the URL */ - UI.initSetting('host', window.location.hostname); - UI.initSetting('port', port); - UI.initSetting('encrypt', (window.location.protocol === "https:")); - UI.initSetting('true_color', true); - UI.initSetting('cursor', !Util.isTouchDevice); - UI.initSetting('clip', false); - UI.initSetting('resize', 'off'); - UI.initSetting('shared', true); - UI.initSetting('view_only', false); - UI.initSetting('path', 'websockify'); - UI.initSetting('repeaterID', ''); - UI.initSetting('reconnect', false); - UI.initSetting('reconnect_delay', 5000); - - UI.setupSettingLabels(); - }, - - // Adds a link to the label elements on the corresponding input elements - setupSettingLabels: function() { - var labels = document.getElementsByTagName('LABEL'); - for (var i = 0; i < labels.length; i++) { - var htmlFor = labels[i].htmlFor; - if (htmlFor != '') { - var elem = document.getElementById(htmlFor); - if (elem) elem.label = labels[i]; - } else { - // If 'for' isn't set, use the first input element child - var children = labels[i].children; - for (var j = 0; j < children.length; j++) { - if (children[j].form !== undefined) { - children[j].label = labels[i]; - break; - } + // If 'for' isn't set, use the first input element child + var children = labels[i].children; + for (var j = 0; j < children.length; j++) { + if (children[j].form !== undefined) { + children[j].label = labels[i]; + break; } } } - }, + } + }, - initRFB: function() { - try { - UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'), - 'onNotification': UI.notification, - 'onUpdateState': UI.updateState, - 'onDisconnected': UI.disconnectFinished, - 'onPasswordRequired': UI.passwordRequired, - 'onXvpInit': UI.updateXvpButton, - 'onClipboard': UI.clipboardReceive, - 'onBell': UI.bell, - 'onFBUComplete': UI.initialResize, - 'onFBResize': UI.updateSessionSize, - 'onDesktopName': UI.updateDesktopName}); - return true; - } catch (exc) { - var msg = "Unable to create RFB client -- " + exc; - Util.Error(msg); - UI.showStatus(msg, 'error'); - return false; - } - }, + initRFB: function() { + try { + UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'), + 'onNotification': UI.notification, + 'onUpdateState': UI.updateState, + 'onDisconnected': UI.disconnectFinished, + 'onPasswordRequired': UI.passwordRequired, + 'onXvpInit': UI.updateXvpButton, + 'onClipboard': UI.clipboardReceive, + 'onBell': UI.bell, + 'onFBUComplete': UI.initialResize, + 'onFBResize': UI.updateSessionSize, + 'onDesktopName': UI.updateDesktopName}); + return true; + } catch (exc) { + var msg = "Unable to create RFB client -- " + exc; + Log.Error(msg); + UI.showStatus(msg, 'error'); + return false; + } + }, /* ------^------- - * /INIT - * ============== - * EVENT HANDLERS - * ------v------*/ +* /INIT +* ============== +* EVENT HANDLERS +* ------v------*/ - addResizeHandlers: function() { - window.addEventListener('resize', UI.applyResizeMode); - window.addEventListener('resize', UI.updateViewClip); - }, + addResizeHandlers: function() { + window.addEventListener('resize', UI.applyResizeMode); + window.addEventListener('resize', UI.updateViewClip); + }, - addControlbarHandlers: function() { - document.getElementById("noVNC_control_bar") - .addEventListener('mousemove', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('mouseup', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('mousedown', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('keypress', UI.activateControlbar); + addControlbarHandlers: function() { + document.getElementById("noVNC_control_bar") + .addEventListener('mousemove', UI.activateControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('mouseup', UI.activateControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('mousedown', UI.activateControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('keypress', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('mousedown', UI.keepControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('keypress', UI.keepControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('mousedown', UI.keepControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('keypress', UI.keepControlbar); - document.getElementById("noVNC_view_drag_button") - .addEventListener('click', UI.toggleViewDrag); + document.getElementById("noVNC_view_drag_button") + .addEventListener('click', UI.toggleViewDrag); - document.getElementById("noVNC_control_bar_handle") - .addEventListener('mousedown', UI.controlbarHandleMouseDown); - document.getElementById("noVNC_control_bar_handle") - .addEventListener('mouseup', UI.controlbarHandleMouseUp); - document.getElementById("noVNC_control_bar_handle") - .addEventListener('mousemove', UI.dragControlbarHandle); - // resize events aren't available for elements - window.addEventListener('resize', UI.updateControlbarHandle); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('mousedown', UI.controlbarHandleMouseDown); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('mouseup', UI.controlbarHandleMouseUp); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('mousemove', UI.dragControlbarHandle); + // resize events aren't available for elements + window.addEventListener('resize', UI.updateControlbarHandle); - var exps = document.getElementsByClassName("noVNC_expander"); - for (var i = 0;i < exps.length;i++) { - exps[i].addEventListener('click', UI.toggleExpander); - } - }, + var exps = document.getElementsByClassName("noVNC_expander"); + for (var i = 0;i < exps.length;i++) { + exps[i].addEventListener('click', UI.toggleExpander); + } + }, - addTouchSpecificHandlers: function() { - document.getElementById("noVNC_mouse_button0") - .addEventListener('click', function () { UI.setMouseButton(1); }); - document.getElementById("noVNC_mouse_button1") - .addEventListener('click', function () { UI.setMouseButton(2); }); - document.getElementById("noVNC_mouse_button2") - .addEventListener('click', function () { UI.setMouseButton(4); }); - document.getElementById("noVNC_mouse_button4") - .addEventListener('click', function () { UI.setMouseButton(0); }); - document.getElementById("noVNC_keyboard_button") - .addEventListener('click', UI.toggleVirtualKeyboard); + addTouchSpecificHandlers: function() { + document.getElementById("noVNC_mouse_button0") + .addEventListener('click', function () { UI.setMouseButton(1); }); + document.getElementById("noVNC_mouse_button1") + .addEventListener('click', function () { UI.setMouseButton(2); }); + document.getElementById("noVNC_mouse_button2") + .addEventListener('click', function () { UI.setMouseButton(4); }); + document.getElementById("noVNC_mouse_button4") + .addEventListener('click', function () { UI.setMouseButton(0); }); + document.getElementById("noVNC_keyboard_button") + .addEventListener('click', UI.toggleVirtualKeyboard); - document.getElementById("noVNC_keyboardinput") - .addEventListener('input', UI.keyInput); - document.getElementById("noVNC_keyboardinput") - .addEventListener('focus', UI.onfocusVirtualKeyboard); - document.getElementById("noVNC_keyboardinput") - .addEventListener('blur', UI.onblurVirtualKeyboard); - document.getElementById("noVNC_keyboardinput") - .addEventListener('submit', function () { return false; }); + document.getElementById("noVNC_keyboardinput") + .addEventListener('input', UI.keyInput); + document.getElementById("noVNC_keyboardinput") + .addEventListener('focus', UI.onfocusVirtualKeyboard); + document.getElementById("noVNC_keyboardinput") + .addEventListener('blur', UI.onblurVirtualKeyboard); + document.getElementById("noVNC_keyboardinput") + .addEventListener('submit', function () { return false; }); - document.documentElement - .addEventListener('mousedown', UI.keepVirtualKeyboard, true); + document.documentElement + .addEventListener('mousedown', UI.keepVirtualKeyboard, true); - document.getElementById("noVNC_control_bar") - .addEventListener('touchstart', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('touchmove', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('touchend', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('input', UI.activateControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('touchstart', UI.activateControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('touchmove', UI.activateControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('touchend', UI.activateControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('input', UI.activateControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('touchstart', UI.keepControlbar); - document.getElementById("noVNC_control_bar") - .addEventListener('input', UI.keepControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('touchstart', UI.keepControlbar); + document.getElementById("noVNC_control_bar") + .addEventListener('input', UI.keepControlbar); - document.getElementById("noVNC_control_bar_handle") - .addEventListener('touchstart', UI.controlbarHandleMouseDown); - document.getElementById("noVNC_control_bar_handle") - .addEventListener('touchend', UI.controlbarHandleMouseUp); - document.getElementById("noVNC_control_bar_handle") - .addEventListener('touchmove', UI.dragControlbarHandle); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('touchstart', UI.controlbarHandleMouseDown); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('touchend', UI.controlbarHandleMouseUp); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('touchmove', UI.dragControlbarHandle); - window.addEventListener('load', UI.keyboardinputReset); - }, + window.addEventListener('load', UI.keyboardinputReset); + }, - addExtraKeysHandlers: function() { - document.getElementById("noVNC_toggle_extra_keys_button") - .addEventListener('click', UI.toggleExtraKeys); - document.getElementById("noVNC_toggle_ctrl_button") - .addEventListener('click', UI.toggleCtrl); - document.getElementById("noVNC_toggle_alt_button") - .addEventListener('click', UI.toggleAlt); - document.getElementById("noVNC_send_tab_button") - .addEventListener('click', UI.sendTab); - document.getElementById("noVNC_send_esc_button") - .addEventListener('click', UI.sendEsc); - document.getElementById("noVNC_send_ctrl_alt_del_button") - .addEventListener('click', UI.sendCtrlAltDel); - }, + addExtraKeysHandlers: function() { + document.getElementById("noVNC_toggle_extra_keys_button") + .addEventListener('click', UI.toggleExtraKeys); + document.getElementById("noVNC_toggle_ctrl_button") + .addEventListener('click', UI.toggleCtrl); + document.getElementById("noVNC_toggle_alt_button") + .addEventListener('click', UI.toggleAlt); + document.getElementById("noVNC_send_tab_button") + .addEventListener('click', UI.sendTab); + document.getElementById("noVNC_send_esc_button") + .addEventListener('click', UI.sendEsc); + document.getElementById("noVNC_send_ctrl_alt_del_button") + .addEventListener('click', UI.sendCtrlAltDel); + }, - addXvpHandlers: function() { - document.getElementById("noVNC_xvp_shutdown_button") - .addEventListener('click', function() { UI.rfb.xvpShutdown(); }); - document.getElementById("noVNC_xvp_reboot_button") - .addEventListener('click', function() { UI.rfb.xvpReboot(); }); - document.getElementById("noVNC_xvp_reset_button") - .addEventListener('click', function() { UI.rfb.xvpReset(); }); - document.getElementById("noVNC_xvp_button") - .addEventListener('click', UI.toggleXvpPanel); - }, + addXvpHandlers: function() { + document.getElementById("noVNC_xvp_shutdown_button") + .addEventListener('click', function() { UI.rfb.xvpShutdown(); }); + document.getElementById("noVNC_xvp_reboot_button") + .addEventListener('click', function() { UI.rfb.xvpReboot(); }); + document.getElementById("noVNC_xvp_reset_button") + .addEventListener('click', function() { UI.rfb.xvpReset(); }); + document.getElementById("noVNC_xvp_button") + .addEventListener('click', UI.toggleXvpPanel); + }, - addConnectionControlHandlers: function() { - document.getElementById("noVNC_disconnect_button") - .addEventListener('click', UI.disconnect); - document.getElementById("noVNC_connect_button") - .addEventListener('click', UI.connect); - document.getElementById("noVNC_cancel_reconnect_button") - .addEventListener('click', UI.cancelReconnect); + addConnectionControlHandlers: function() { + document.getElementById("noVNC_disconnect_button") + .addEventListener('click', UI.disconnect); + document.getElementById("noVNC_connect_button") + .addEventListener('click', UI.connect); + document.getElementById("noVNC_cancel_reconnect_button") + .addEventListener('click', UI.cancelReconnect); - document.getElementById("noVNC_password_button") - .addEventListener('click', UI.setPassword); - }, + document.getElementById("noVNC_password_button") + .addEventListener('click', UI.setPassword); + }, - addClipboardHandlers: function() { - document.getElementById("noVNC_clipboard_button") - .addEventListener('click', UI.toggleClipboardPanel); - document.getElementById("noVNC_clipboard_text") - .addEventListener('focus', UI.displayBlur); - document.getElementById("noVNC_clipboard_text") - .addEventListener('blur', UI.displayFocus); - document.getElementById("noVNC_clipboard_text") - .addEventListener('change', UI.clipboardSend); - document.getElementById("noVNC_clipboard_clear_button") - .addEventListener('click', UI.clipboardClear); - }, + addClipboardHandlers: function() { + document.getElementById("noVNC_clipboard_button") + .addEventListener('click', UI.toggleClipboardPanel); + document.getElementById("noVNC_clipboard_text") + .addEventListener('focus', UI.displayBlur); + document.getElementById("noVNC_clipboard_text") + .addEventListener('blur', UI.displayFocus); + document.getElementById("noVNC_clipboard_text") + .addEventListener('change', UI.clipboardSend); + document.getElementById("noVNC_clipboard_clear_button") + .addEventListener('click', UI.clipboardClear); + }, - // Add a call to save settings when the element changes, - // unless the optional parameter changeFunc is used instead. - addSettingChangeHandler: function(name, changeFunc) { - var settingElem = document.getElementById("noVNC_setting_" + name); - if (changeFunc === undefined) { - changeFunc = function () { UI.saveSetting(name); }; - } - settingElem.addEventListener('change', changeFunc); - }, + // Add a call to save settings when the element changes, + // unless the optional parameter changeFunc is used instead. + addSettingChangeHandler: function(name, changeFunc) { + var settingElem = document.getElementById("noVNC_setting_" + name); + if (changeFunc === undefined) { + changeFunc = function () { UI.saveSetting(name); }; + } + settingElem.addEventListener('change', changeFunc); + }, - addSettingsHandlers: function() { - document.getElementById("noVNC_settings_button") - .addEventListener('click', UI.toggleSettingsPanel); + addSettingsHandlers: function() { + document.getElementById("noVNC_settings_button") + .addEventListener('click', UI.toggleSettingsPanel); - UI.addSettingChangeHandler('encrypt'); - UI.addSettingChangeHandler('true_color'); - UI.addSettingChangeHandler('cursor'); - UI.addSettingChangeHandler('cursor', UI.updateLocalCursor); - UI.addSettingChangeHandler('resize'); - UI.addSettingChangeHandler('resize', UI.enableDisableViewClip); - UI.addSettingChangeHandler('resize', UI.applyResizeMode); - UI.addSettingChangeHandler('clip'); - UI.addSettingChangeHandler('clip', UI.updateViewClip); - UI.addSettingChangeHandler('shared'); - UI.addSettingChangeHandler('view_only'); - UI.addSettingChangeHandler('view_only', UI.updateViewOnly); - UI.addSettingChangeHandler('host'); - UI.addSettingChangeHandler('port'); - UI.addSettingChangeHandler('path'); - UI.addSettingChangeHandler('repeaterID'); - UI.addSettingChangeHandler('logging'); - UI.addSettingChangeHandler('logging', UI.updateLogging); - UI.addSettingChangeHandler('reconnect'); - UI.addSettingChangeHandler('reconnect_delay'); - }, + UI.addSettingChangeHandler('encrypt'); + UI.addSettingChangeHandler('true_color'); + UI.addSettingChangeHandler('cursor'); + UI.addSettingChangeHandler('cursor', UI.updateLocalCursor); + UI.addSettingChangeHandler('resize'); + UI.addSettingChangeHandler('resize', UI.enableDisableViewClip); + UI.addSettingChangeHandler('resize', UI.applyResizeMode); + UI.addSettingChangeHandler('clip'); + UI.addSettingChangeHandler('clip', UI.updateViewClip); + UI.addSettingChangeHandler('shared'); + UI.addSettingChangeHandler('view_only'); + UI.addSettingChangeHandler('view_only', UI.updateViewOnly); + UI.addSettingChangeHandler('host'); + UI.addSettingChangeHandler('port'); + UI.addSettingChangeHandler('path'); + UI.addSettingChangeHandler('repeaterID'); + UI.addSettingChangeHandler('logging'); + UI.addSettingChangeHandler('logging', UI.updateLogging); + UI.addSettingChangeHandler('reconnect'); + UI.addSettingChangeHandler('reconnect_delay'); + }, - addFullscreenHandlers: function() { - document.getElementById("noVNC_fullscreen_button") - .addEventListener('click', UI.toggleFullscreen); + addFullscreenHandlers: function() { + document.getElementById("noVNC_fullscreen_button") + .addEventListener('click', UI.toggleFullscreen); - window.addEventListener('fullscreenchange', UI.updateFullscreenButton); - window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton); - window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton); - window.addEventListener('msfullscreenchange', UI.updateFullscreenButton); - }, + window.addEventListener('fullscreenchange', UI.updateFullscreenButton); + window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton); + window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton); + window.addEventListener('msfullscreenchange', UI.updateFullscreenButton); + }, /* ------^------- * /EVENT HANDLERS @@ -455,341 +458,341 @@ var UI; * VISUAL * ------v------*/ - updateState: function(rfb, state, oldstate) { - var msg; + updateState: function(rfb, state, oldstate) { + var msg; - document.documentElement.classList.remove("noVNC_connecting"); - document.documentElement.classList.remove("noVNC_connected"); - document.documentElement.classList.remove("noVNC_disconnecting"); - document.documentElement.classList.remove("noVNC_reconnecting"); + document.documentElement.classList.remove("noVNC_connecting"); + document.documentElement.classList.remove("noVNC_connected"); + document.documentElement.classList.remove("noVNC_disconnecting"); + document.documentElement.classList.remove("noVNC_reconnecting"); - switch (state) { - case 'connecting': - document.getElementById("noVNC_transition_text").textContent = _("Connecting..."); - document.documentElement.classList.add("noVNC_connecting"); - break; - case 'connected': - UI.connected = true; - UI.inhibit_reconnect = false; - document.documentElement.classList.add("noVNC_connected"); - if (rfb && rfb.get_encrypt()) { - msg = _("Connected (encrypted) to ") + UI.desktopName; - } else { - msg = _("Connected (unencrypted) to ") + UI.desktopName; - } - UI.showStatus(msg); - break; - case 'disconnecting': - UI.connected = false; - document.getElementById("noVNC_transition_text").textContent = _("Disconnecting..."); - document.documentElement.classList.add("noVNC_disconnecting"); - break; - case 'disconnected': - UI.showStatus(_("Disconnected")); - break; - default: - msg = "Invalid UI state"; - Util.Error(msg); - UI.showStatus(msg, 'error'); - break; - } + switch (state) { + case 'connecting': + document.getElementById("noVNC_transition_text").textContent = _("Connecting..."); + document.documentElement.classList.add("noVNC_connecting"); + break; + case 'connected': + UI.connected = true; + UI.inhibit_reconnect = false; + document.documentElement.classList.add("noVNC_connected"); + if (rfb && rfb.get_encrypt()) { + msg = _("Connected (encrypted) to ") + UI.desktopName; + } else { + msg = _("Connected (unencrypted) to ") + UI.desktopName; + } + UI.showStatus(msg); + break; + case 'disconnecting': + UI.connected = false; + document.getElementById("noVNC_transition_text").textContent = _("Disconnecting..."); + document.documentElement.classList.add("noVNC_disconnecting"); + break; + case 'disconnected': + UI.showStatus(_("Disconnected")); + break; + default: + msg = "Invalid UI state"; + Log.Error(msg); + UI.showStatus(msg, 'error'); + break; + } - UI.updateVisualState(); - }, + UI.updateVisualState(); + }, - // Disable/enable controls depending on connection state - updateVisualState: function() { - //Util.Debug(">> updateVisualState"); + // Disable/enable controls depending on connection state + updateVisualState: function() { + //Log.Debug(">> updateVisualState"); - UI.enableDisableViewClip(); + UI.enableDisableViewClip(); - if (Util.browserSupportsCursorURIs() && !Util.isTouchDevice) { - UI.enableSetting('cursor'); - } else { - UI.disableSetting('cursor'); - } + if (cursorURIsSupported() && !isTouchDevice) { + UI.enableSetting('cursor'); + } else { + UI.disableSetting('cursor'); + } - if (UI.connected) { - UI.disableSetting('encrypt'); - UI.disableSetting('true_color'); - UI.disableSetting('shared'); - UI.disableSetting('host'); - UI.disableSetting('port'); - UI.disableSetting('path'); - UI.disableSetting('repeaterID'); - UI.updateViewClip(); - UI.setMouseButton(1); + if (UI.connected) { + UI.disableSetting('encrypt'); + UI.disableSetting('true_color'); + UI.disableSetting('shared'); + UI.disableSetting('host'); + UI.disableSetting('port'); + UI.disableSetting('path'); + UI.disableSetting('repeaterID'); + UI.updateViewClip(); + UI.setMouseButton(1); - // Hide the controlbar after 2 seconds - UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000); - } else { - UI.enableSetting('encrypt'); - UI.enableSetting('true_color'); - UI.enableSetting('shared'); - UI.enableSetting('host'); - UI.enableSetting('port'); - UI.enableSetting('path'); - UI.enableSetting('repeaterID'); - UI.updateXvpButton(0); - UI.keepControlbar(); - } + // Hide the controlbar after 2 seconds + UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000); + } else { + UI.enableSetting('encrypt'); + UI.enableSetting('true_color'); + UI.enableSetting('shared'); + UI.enableSetting('host'); + UI.enableSetting('port'); + UI.enableSetting('path'); + UI.enableSetting('repeaterID'); + UI.updateXvpButton(0); + UI.keepControlbar(); + } - // Hide input related buttons in view only mode - if (UI.rfb && UI.rfb.get_view_only()) { - document.getElementById('noVNC_keyboard_button') - .classList.add('noVNC_hidden'); - document.getElementById('noVNC_toggle_extra_keys_button') - .classList.add('noVNC_hidden'); - } else { - document.getElementById('noVNC_keyboard_button') - .classList.remove('noVNC_hidden'); - document.getElementById('noVNC_toggle_extra_keys_button') - .classList.remove('noVNC_hidden'); - } + // Hide input related buttons in view only mode + if (UI.rfb && UI.rfb.get_view_only()) { + document.getElementById('noVNC_keyboard_button') + .classList.add('noVNC_hidden'); + document.getElementById('noVNC_toggle_extra_keys_button') + .classList.add('noVNC_hidden'); + } else { + document.getElementById('noVNC_keyboard_button') + .classList.remove('noVNC_hidden'); + document.getElementById('noVNC_toggle_extra_keys_button') + .classList.remove('noVNC_hidden'); + } - // State change disables viewport dragging. - // It is enabled (toggled) by direct click on the button - UI.setViewDrag(false); + // State change disables viewport dragging. + // It is enabled (toggled) by direct click on the button + UI.setViewDrag(false); - // State change also closes the password dialog - document.getElementById('noVNC_password_dlg') - .classList.remove('noVNC_open'); + // State change also closes the password dialog + document.getElementById('noVNC_password_dlg') + .classList.remove('noVNC_open'); - //Util.Debug("<< updateVisualState"); - }, + //Log.Debug("<< updateVisualState"); + }, - showStatus: function(text, status_type, time) { - var statusElem = document.getElementById('noVNC_status'); + showStatus: function(text, status_type, time) { + var statusElem = document.getElementById('noVNC_status'); - clearTimeout(UI.statusTimeout); + clearTimeout(UI.statusTimeout); - if (typeof status_type === 'undefined') { - status_type = 'normal'; - } + if (typeof status_type === 'undefined') { + status_type = 'normal'; + } - statusElem.classList.remove("noVNC_status_normal"); - statusElem.classList.remove("noVNC_status_warn"); - statusElem.classList.remove("noVNC_status_error"); + statusElem.classList.remove("noVNC_status_normal"); + statusElem.classList.remove("noVNC_status_warn"); + statusElem.classList.remove("noVNC_status_error"); - switch (status_type) { - case 'warning': - case 'warn': - statusElem.classList.add("noVNC_status_warn"); - break; - case 'error': - statusElem.classList.add("noVNC_status_error"); - break; - case 'normal': - case 'info': - default: - statusElem.classList.add("noVNC_status_normal"); - break; - } + switch (status_type) { + case 'warning': + case 'warn': + statusElem.classList.add("noVNC_status_warn"); + break; + case 'error': + statusElem.classList.add("noVNC_status_error"); + break; + case 'normal': + case 'info': + default: + statusElem.classList.add("noVNC_status_normal"); + break; + } - statusElem.textContent = text; - statusElem.classList.add("noVNC_open"); + statusElem.textContent = text; + statusElem.classList.add("noVNC_open"); - // If no time was specified, show the status for 1.5 seconds - if (typeof time === 'undefined') { - time = 1500; - } + // If no time was specified, show the status for 1.5 seconds + if (typeof time === 'undefined') { + time = 1500; + } - // Error messages do not timeout - if (status_type !== 'error') { - UI.statusTimeout = window.setTimeout(UI.hideStatus, time); - } - }, + // Error messages do not timeout + if (status_type !== 'error') { + UI.statusTimeout = window.setTimeout(UI.hideStatus, time); + } + }, - hideStatus: function() { - clearTimeout(UI.statusTimeout); - document.getElementById('noVNC_status').classList.remove("noVNC_open"); - }, + hideStatus: function() { + clearTimeout(UI.statusTimeout); + document.getElementById('noVNC_status').classList.remove("noVNC_open"); + }, - notification: function (rfb, msg, level, options) { - UI.showStatus(msg, level); - }, + notification: function (rfb, msg, level, options) { + UI.showStatus(msg, level); + }, - activateControlbar: function(event) { - clearTimeout(UI.idleControlbarTimeout); - // We manipulate the anchor instead of the actual control - // bar in order to avoid creating new a stacking group - document.getElementById('noVNC_control_bar_anchor') - .classList.remove("noVNC_idle"); - UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000); - }, + activateControlbar: function(event) { + clearTimeout(UI.idleControlbarTimeout); + // We manipulate the anchor instead of the actual control + // bar in order to avoid creating new a stacking group + document.getElementById('noVNC_control_bar_anchor') + .classList.remove("noVNC_idle"); + UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000); + }, - idleControlbar: function() { - document.getElementById('noVNC_control_bar_anchor') - .classList.add("noVNC_idle"); - }, + idleControlbar: function() { + document.getElementById('noVNC_control_bar_anchor') + .classList.add("noVNC_idle"); + }, - keepControlbar: function() { - clearTimeout(UI.closeControlbarTimeout); - }, + keepControlbar: function() { + clearTimeout(UI.closeControlbarTimeout); + }, - openControlbar: function() { - document.getElementById('noVNC_control_bar') - .classList.add("noVNC_open"); - }, + openControlbar: function() { + document.getElementById('noVNC_control_bar') + .classList.add("noVNC_open"); + }, - closeControlbar: function() { - UI.closeAllPanels(); - document.getElementById('noVNC_control_bar') - .classList.remove("noVNC_open"); - }, + closeControlbar: function() { + UI.closeAllPanels(); + document.getElementById('noVNC_control_bar') + .classList.remove("noVNC_open"); + }, - toggleControlbar: function() { - if (document.getElementById('noVNC_control_bar') - .classList.contains("noVNC_open")) { - UI.closeControlbar(); - } else { - UI.openControlbar(); - } - }, + toggleControlbar: function() { + if (document.getElementById('noVNC_control_bar') + .classList.contains("noVNC_open")) { + UI.closeControlbar(); + } else { + UI.openControlbar(); + } + }, - toggleControlbarSide: function () { - // Temporarily disable animation to avoid weird movement - var bar = document.getElementById('noVNC_control_bar'); - bar.style.transitionDuration = '0s'; - bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; }); + toggleControlbarSide: function () { + // Temporarily disable animation to avoid weird movement + var bar = document.getElementById('noVNC_control_bar'); + bar.style.transitionDuration = '0s'; + bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; }); - var anchor = document.getElementById('noVNC_control_bar_anchor'); + var anchor = document.getElementById('noVNC_control_bar_anchor'); + if (anchor.classList.contains("noVNC_right")) { + WebUtil.writeSetting('controlbar_pos', 'left'); + anchor.classList.remove("noVNC_right"); + } else { + WebUtil.writeSetting('controlbar_pos', 'right'); + anchor.classList.add("noVNC_right"); + } + + // Consider this a movement of the handle + UI.controlbarDrag = true; + }, + + dragControlbarHandle: function (e) { + if (!UI.controlbarGrabbed) return; + + var ptr = getPointerEvent(e); + + var anchor = document.getElementById('noVNC_control_bar_anchor'); + if (ptr.clientX < (window.innerWidth * 0.1)) { if (anchor.classList.contains("noVNC_right")) { - WebUtil.writeSetting('controlbar_pos', 'left'); - anchor.classList.remove("noVNC_right"); - } else { - WebUtil.writeSetting('controlbar_pos', 'right'); - anchor.classList.add("noVNC_right"); + UI.toggleControlbarSide(); } + } else if (ptr.clientX > (window.innerWidth * 0.9)) { + if (!anchor.classList.contains("noVNC_right")) { + UI.toggleControlbarSide(); + } + } + + if (!UI.controlbarDrag) { + // The goal is to trigger on a certain physical width, the + // devicePixelRatio brings us a bit closer but is not optimal. + var dragThreshold = 10 * (window.devicePixelRatio || 1); + var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY); + + if (dragDistance < dragThreshold) return; - // Consider this a movement of the handle UI.controlbarDrag = true; - }, + } - dragControlbarHandle: function (e) { - if (!UI.controlbarGrabbed) return; + var eventY = ptr.clientY - UI.controlbarMouseDownOffsetY; - var ptr = Util.getPointerEvent(e); + UI.moveControlbarHandle(eventY); - var anchor = document.getElementById('noVNC_control_bar_anchor'); - if (ptr.clientX < (window.innerWidth * 0.1)) { - if (anchor.classList.contains("noVNC_right")) { - UI.toggleControlbarSide(); - } - } else if (ptr.clientX > (window.innerWidth * 0.9)) { - if (!anchor.classList.contains("noVNC_right")) { - UI.toggleControlbarSide(); - } - } + e.preventDefault(); + e.stopPropagation(); + UI.keepControlbar(); + UI.activateControlbar(); + }, - if (!UI.controlbarDrag) { - // The goal is to trigger on a certain physical width, the - // devicePixelRatio brings us a bit closer but is not optimal. - var dragThreshold = 10 * (window.devicePixelRatio || 1); - var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY); + // Move the handle but don't allow any position outside the bounds + moveControlbarHandle: function (viewportRelativeY) { + var handle = document.getElementById("noVNC_control_bar_handle"); + var handleHeight = handle.getBoundingClientRect().height; + var controlbarBounds = document.getElementById("noVNC_control_bar") + .getBoundingClientRect(); + var margin = 10; - if (dragDistance < dragThreshold) return; + // These heights need to be non-zero for the below logic to work + if (handleHeight === 0 || controlbarBounds.height === 0) { + return; + } - UI.controlbarDrag = true; - } + var newY = viewportRelativeY; - var eventY = ptr.clientY - UI.controlbarMouseDownOffsetY; + // Check if the coordinates are outside the control bar + if (newY < controlbarBounds.top + margin) { + // Force coordinates to be below the top of the control bar + newY = controlbarBounds.top + margin; - UI.moveControlbarHandle(eventY); + } else if (newY > controlbarBounds.top + + controlbarBounds.height - handleHeight - margin) { + // Force coordinates to be above the bottom of the control bar + newY = controlbarBounds.top + + controlbarBounds.height - handleHeight - margin; + } + // Corner case: control bar too small for stable position + if (controlbarBounds.height < (handleHeight + margin * 2)) { + newY = controlbarBounds.top + + (controlbarBounds.height - handleHeight) / 2; + } + + // The transform needs coordinates that are relative to the parent + var parentRelativeY = newY - controlbarBounds.top; + handle.style.transform = "translateY(" + parentRelativeY + "px)"; + }, + + updateControlbarHandle: function () { + // Since the control bar is fixed on the viewport and not the page, + // the move function expects coordinates relative the the viewport. + var handle = document.getElementById("noVNC_control_bar_handle"); + var handleBounds = handle.getBoundingClientRect(); + UI.moveControlbarHandle(handleBounds.top); + }, + + controlbarHandleMouseUp: function(e) { + if ((e.type == "mouseup") && (e.button != 0)) return; + + // mouseup and mousedown on the same place toggles the controlbar + if (UI.controlbarGrabbed && !UI.controlbarDrag) { + UI.toggleControlbar(); e.preventDefault(); e.stopPropagation(); UI.keepControlbar(); UI.activateControlbar(); - }, + } + UI.controlbarGrabbed = false; + }, - // Move the handle but don't allow any position outside the bounds - moveControlbarHandle: function (viewportRelativeY) { - var handle = document.getElementById("noVNC_control_bar_handle"); - var handleHeight = handle.getBoundingClientRect().height; - var controlbarBounds = document.getElementById("noVNC_control_bar") - .getBoundingClientRect(); - var margin = 10; + controlbarHandleMouseDown: function(e) { + if ((e.type == "mousedown") && (e.button != 0)) return; - // These heights need to be non-zero for the below logic to work - if (handleHeight === 0 || controlbarBounds.height === 0) { - return; - } + var ptr = getPointerEvent(e); - var newY = viewportRelativeY; + var handle = document.getElementById("noVNC_control_bar_handle"); + var bounds = handle.getBoundingClientRect(); - // Check if the coordinates are outside the control bar - if (newY < controlbarBounds.top + margin) { - // Force coordinates to be below the top of the control bar - newY = controlbarBounds.top + margin; + setCapture(handle); + UI.controlbarGrabbed = true; + UI.controlbarDrag = false; - } else if (newY > controlbarBounds.top + - controlbarBounds.height - handleHeight - margin) { - // Force coordinates to be above the bottom of the control bar - newY = controlbarBounds.top + - controlbarBounds.height - handleHeight - margin; - } + UI.controlbarMouseDownClientY = ptr.clientY; + UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top; + e.preventDefault(); + e.stopPropagation(); + UI.keepControlbar(); + UI.activateControlbar(); + }, - // Corner case: control bar too small for stable position - if (controlbarBounds.height < (handleHeight + margin * 2)) { - newY = controlbarBounds.top + - (controlbarBounds.height - handleHeight) / 2; - } - - // The transform needs coordinates that are relative to the parent - var parentRelativeY = newY - controlbarBounds.top; - handle.style.transform = "translateY(" + parentRelativeY + "px)"; - }, - - updateControlbarHandle: function () { - // Since the control bar is fixed on the viewport and not the page, - // the move function expects coordinates relative the the viewport. - var handle = document.getElementById("noVNC_control_bar_handle"); - var handleBounds = handle.getBoundingClientRect(); - UI.moveControlbarHandle(handleBounds.top); - }, - - controlbarHandleMouseUp: function(e) { - if ((e.type == "mouseup") && (e.button != 0)) return; - - // mouseup and mousedown on the same place toggles the controlbar - if (UI.controlbarGrabbed && !UI.controlbarDrag) { - UI.toggleControlbar(); - e.preventDefault(); - e.stopPropagation(); - UI.keepControlbar(); - UI.activateControlbar(); - } - UI.controlbarGrabbed = false; - }, - - controlbarHandleMouseDown: function(e) { - if ((e.type == "mousedown") && (e.button != 0)) return; - - var ptr = Util.getPointerEvent(e); - - var handle = document.getElementById("noVNC_control_bar_handle"); - var bounds = handle.getBoundingClientRect(); - - Util.setCapture(handle); - UI.controlbarGrabbed = true; - UI.controlbarDrag = false; - - UI.controlbarMouseDownClientY = ptr.clientY; - UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top; - e.preventDefault(); - e.stopPropagation(); - UI.keepControlbar(); - UI.activateControlbar(); - }, - - toggleExpander: function(e) { - if (this.classList.contains("noVNC_open")) { - this.classList.remove("noVNC_open"); - } else { - this.classList.add("noVNC_open"); - } - }, + toggleExpander: function(e) { + if (this.classList.contains("noVNC_open")) { + this.classList.remove("noVNC_open"); + } else { + this.classList.add("noVNC_open"); + } + }, /* ------^------- * /VISUAL @@ -797,93 +800,93 @@ var UI; * SETTINGS * ------v------*/ - // Initial page load read/initialization of settings - initSetting: function(name, defVal) { - // Check Query string followed by cookie - var val = WebUtil.getConfigVar(name); - if (val === null) { - val = WebUtil.readSetting(name, defVal); - } - UI.updateSetting(name, val); - return val; - }, + // Initial page load read/initialization of settings + initSetting: function(name, defVal) { + // Check Query string followed by cookie + var val = WebUtil.getConfigVar(name); + if (val === null) { + val = WebUtil.readSetting(name, defVal); + } + UI.updateSetting(name, val); + return val; + }, - // Update cookie and form control setting. If value is not set, then - // updates from control to current cookie setting. - updateSetting: function(name, value) { + // Update cookie and form control setting. If value is not set, then + // updates from control to current cookie setting. + updateSetting: function(name, value) { - // Save the cookie for this session - if (typeof value !== 'undefined') { - WebUtil.writeSetting(name, value); - } + // Save the cookie for this session + if (typeof value !== 'undefined') { + WebUtil.writeSetting(name, value); + } - // Update the settings control - value = UI.getSetting(name); + // Update the settings control + value = UI.getSetting(name); - var ctrl = document.getElementById('noVNC_setting_' + name); - if (ctrl.type === 'checkbox') { - ctrl.checked = value; + var ctrl = document.getElementById('noVNC_setting_' + name); + if (ctrl.type === 'checkbox') { + ctrl.checked = value; - } else if (typeof ctrl.options !== 'undefined') { - for (var i = 0; i < ctrl.options.length; i += 1) { - if (ctrl.options[i].value === value) { - ctrl.selectedIndex = i; - break; - } + } else if (typeof ctrl.options !== 'undefined') { + for (var i = 0; i < ctrl.options.length; i += 1) { + if (ctrl.options[i].value === value) { + ctrl.selectedIndex = i; + break; } + } + } else { + /*Weird IE9 error leads to 'null' appearring + in textboxes instead of ''.*/ + if (value === null) { + value = ""; + } + ctrl.value = value; + } + }, + + // Save control setting to cookie + saveSetting: function(name) { + var val, ctrl = document.getElementById('noVNC_setting_' + name); + if (ctrl.type === 'checkbox') { + val = ctrl.checked; + } else if (typeof ctrl.options !== 'undefined') { + val = ctrl.options[ctrl.selectedIndex].value; + } else { + val = ctrl.value; + } + WebUtil.writeSetting(name, val); + //Log.Debug("Setting saved '" + name + "=" + val + "'"); + return val; + }, + + // Read form control compatible setting from cookie + getSetting: function(name) { + var ctrl = document.getElementById('noVNC_setting_' + name); + var val = WebUtil.readSetting(name); + if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') { + if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) { + val = false; } else { - /*Weird IE9 error leads to 'null' appearring - in textboxes instead of ''.*/ - if (value === null) { - value = ""; - } - ctrl.value = value; + val = true; } - }, + } + return val; + }, - // Save control setting to cookie - saveSetting: function(name) { - var val, ctrl = document.getElementById('noVNC_setting_' + name); - if (ctrl.type === 'checkbox') { - val = ctrl.checked; - } else if (typeof ctrl.options !== 'undefined') { - val = ctrl.options[ctrl.selectedIndex].value; - } else { - val = ctrl.value; - } - WebUtil.writeSetting(name, val); - //Util.Debug("Setting saved '" + name + "=" + val + "'"); - return val; - }, + // These helpers compensate for the lack of parent-selectors and + // previous-sibling-selectors in CSS which are needed when we want to + // disable the labels that belong to disabled input elements. + disableSetting: function(name) { + var ctrl = document.getElementById('noVNC_setting_' + name); + ctrl.disabled = true; + ctrl.label.classList.add('noVNC_disabled'); + }, - // Read form control compatible setting from cookie - getSetting: function(name) { - var ctrl = document.getElementById('noVNC_setting_' + name); - var val = WebUtil.readSetting(name); - if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') { - if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) { - val = false; - } else { - val = true; - } - } - return val; - }, - - // These helpers compensate for the lack of parent-selectors and - // previous-sibling-selectors in CSS which are needed when we want to - // disable the labels that belong to disabled input elements. - disableSetting: function(name) { - var ctrl = document.getElementById('noVNC_setting_' + name); - ctrl.disabled = true; - ctrl.label.classList.add('noVNC_disabled'); - }, - - enableSetting: function(name) { - var ctrl = document.getElementById('noVNC_setting_' + name); - ctrl.disabled = false; - ctrl.label.classList.remove('noVNC_disabled'); - }, + enableSetting: function(name) { + var ctrl = document.getElementById('noVNC_setting_' + name); + ctrl.disabled = false; + ctrl.label.classList.remove('noVNC_disabled'); + }, /* ------^------- * /SETTINGS @@ -891,12 +894,12 @@ var UI; * PANELS * ------v------*/ - closeAllPanels: function() { - UI.closeSettingsPanel(); - UI.closeXvpPanel(); - UI.closeClipboardPanel(); - UI.closeExtraKeys(); - }, + closeAllPanels: function() { + UI.closeSettingsPanel(); + UI.closeXvpPanel(); + UI.closeClipboardPanel(); + UI.closeExtraKeys(); + }, /* ------^------- * /PANELS @@ -904,50 +907,50 @@ var UI; * SETTINGS (panel) * ------v------*/ - openSettingsPanel: function() { - UI.closeAllPanels(); - UI.openControlbar(); + openSettingsPanel: function() { + UI.closeAllPanels(); + UI.openControlbar(); - // Refresh UI elements from saved cookies - UI.updateSetting('encrypt'); - UI.updateSetting('true_color'); - if (Util.browserSupportsCursorURIs()) { - UI.updateSetting('cursor'); - } else { - UI.updateSetting('cursor', !Util.isTouchDevice); - UI.disableSetting('cursor'); - } - UI.updateSetting('clip'); - UI.updateSetting('resize'); - UI.updateSetting('shared'); - UI.updateSetting('view_only'); - UI.updateSetting('path'); - UI.updateSetting('repeaterID'); - UI.updateSetting('logging'); - UI.updateSetting('reconnect'); - UI.updateSetting('reconnect_delay'); + // Refresh UI elements from saved cookies + UI.updateSetting('encrypt'); + UI.updateSetting('true_color'); + if (cursorURIsSupported()) { + UI.updateSetting('cursor'); + } else { + UI.updateSetting('cursor', !isTouchDevice); + UI.disableSetting('cursor'); + } + UI.updateSetting('clip'); + UI.updateSetting('resize'); + UI.updateSetting('shared'); + UI.updateSetting('view_only'); + UI.updateSetting('path'); + UI.updateSetting('repeaterID'); + UI.updateSetting('logging'); + UI.updateSetting('reconnect'); + UI.updateSetting('reconnect_delay'); - document.getElementById('noVNC_settings') - .classList.add("noVNC_open"); - document.getElementById('noVNC_settings_button') - .classList.add("noVNC_selected"); - }, + document.getElementById('noVNC_settings') + .classList.add("noVNC_open"); + document.getElementById('noVNC_settings_button') + .classList.add("noVNC_selected"); + }, - closeSettingsPanel: function() { - document.getElementById('noVNC_settings') - .classList.remove("noVNC_open"); - document.getElementById('noVNC_settings_button') - .classList.remove("noVNC_selected"); - }, + closeSettingsPanel: function() { + document.getElementById('noVNC_settings') + .classList.remove("noVNC_open"); + document.getElementById('noVNC_settings_button') + .classList.remove("noVNC_selected"); + }, - toggleSettingsPanel: function() { - if (document.getElementById('noVNC_settings') - .classList.contains("noVNC_open")) { - UI.closeSettingsPanel(); - } else { - UI.openSettingsPanel(); - } - }, + toggleSettingsPanel: function() { + if (document.getElementById('noVNC_settings') + .classList.contains("noVNC_open")) { + UI.closeSettingsPanel(); + } else { + UI.openSettingsPanel(); + } + }, /* ------^------- * /SETTINGS @@ -955,44 +958,44 @@ var UI; * XVP * ------v------*/ - openXvpPanel: function() { - UI.closeAllPanels(); - UI.openControlbar(); + openXvpPanel: function() { + UI.closeAllPanels(); + UI.openControlbar(); - document.getElementById('noVNC_xvp') - .classList.add("noVNC_open"); + document.getElementById('noVNC_xvp') + .classList.add("noVNC_open"); + document.getElementById('noVNC_xvp_button') + .classList.add("noVNC_selected"); + }, + + closeXvpPanel: function() { + document.getElementById('noVNC_xvp') + .classList.remove("noVNC_open"); + document.getElementById('noVNC_xvp_button') + .classList.remove("noVNC_selected"); + }, + + toggleXvpPanel: function() { + if (document.getElementById('noVNC_xvp') + .classList.contains("noVNC_open")) { + UI.closeXvpPanel(); + } else { + UI.openXvpPanel(); + } + }, + + // Disable/enable XVP button + updateXvpButton: function(ver) { + if (ver >= 1 && !UI.rfb.get_view_only()) { document.getElementById('noVNC_xvp_button') - .classList.add("noVNC_selected"); - }, - - closeXvpPanel: function() { - document.getElementById('noVNC_xvp') - .classList.remove("noVNC_open"); + .classList.remove("noVNC_hidden"); + } else { document.getElementById('noVNC_xvp_button') - .classList.remove("noVNC_selected"); - }, - - toggleXvpPanel: function() { - if (document.getElementById('noVNC_xvp') - .classList.contains("noVNC_open")) { - UI.closeXvpPanel(); - } else { - UI.openXvpPanel(); - } - }, - - // Disable/enable XVP button - updateXvpButton: function(ver) { - if (ver >= 1 && !UI.rfb.get_view_only()) { - document.getElementById('noVNC_xvp_button') - .classList.remove("noVNC_hidden"); - } else { - document.getElementById('noVNC_xvp_button') - .classList.add("noVNC_hidden"); - // Close XVP panel if open - UI.closeXvpPanel(); - } - }, + .classList.add("noVNC_hidden"); + // Close XVP panel if open + UI.closeXvpPanel(); + } + }, /* ------^------- * /XVP @@ -1000,49 +1003,49 @@ var UI; * CLIPBOARD * ------v------*/ - openClipboardPanel: function() { - UI.closeAllPanels(); - UI.openControlbar(); + openClipboardPanel: function() { + UI.closeAllPanels(); + UI.openControlbar(); - document.getElementById('noVNC_clipboard') - .classList.add("noVNC_open"); - document.getElementById('noVNC_clipboard_button') - .classList.add("noVNC_selected"); - }, + document.getElementById('noVNC_clipboard') + .classList.add("noVNC_open"); + document.getElementById('noVNC_clipboard_button') + .classList.add("noVNC_selected"); + }, - closeClipboardPanel: function() { - document.getElementById('noVNC_clipboard') - .classList.remove("noVNC_open"); - document.getElementById('noVNC_clipboard_button') - .classList.remove("noVNC_selected"); - }, + closeClipboardPanel: function() { + document.getElementById('noVNC_clipboard') + .classList.remove("noVNC_open"); + document.getElementById('noVNC_clipboard_button') + .classList.remove("noVNC_selected"); + }, - toggleClipboardPanel: function() { - if (document.getElementById('noVNC_clipboard') - .classList.contains("noVNC_open")) { - UI.closeClipboardPanel(); - } else { - UI.openClipboardPanel(); - } - }, + toggleClipboardPanel: function() { + if (document.getElementById('noVNC_clipboard') + .classList.contains("noVNC_open")) { + UI.closeClipboardPanel(); + } else { + UI.openClipboardPanel(); + } + }, - clipboardReceive: function(rfb, text) { - Util.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "..."); - document.getElementById('noVNC_clipboard_text').value = text; - Util.Debug("<< UI.clipboardReceive"); - }, + clipboardReceive: function(rfb, text) { + Log.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "..."); + document.getElementById('noVNC_clipboard_text').value = text; + Log.Debug("<< UI.clipboardReceive"); + }, - clipboardClear: function() { - document.getElementById('noVNC_clipboard_text').value = ""; - UI.rfb.clipboardPasteFrom(""); - }, + clipboardClear: function() { + document.getElementById('noVNC_clipboard_text').value = ""; + UI.rfb.clipboardPasteFrom(""); + }, - clipboardSend: function() { - var text = document.getElementById('noVNC_clipboard_text').value; - Util.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "..."); - UI.rfb.clipboardPasteFrom(text); - Util.Debug("<< UI.clipboardSend"); - }, + clipboardSend: function() { + var text = document.getElementById('noVNC_clipboard_text').value; + Log.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "..."); + UI.rfb.clipboardPasteFrom(text); + Log.Debug("<< UI.clipboardSend"); + }, /* ------^------- * /CLIPBOARD @@ -1050,102 +1053,102 @@ var UI; * CONNECTION * ------v------*/ - openConnectPanel: function() { - document.getElementById('noVNC_connect_dlg') - .classList.add("noVNC_open"); - }, + openConnectPanel: function() { + document.getElementById('noVNC_connect_dlg') + .classList.add("noVNC_open"); + }, - closeConnectPanel: function() { - document.getElementById('noVNC_connect_dlg') - .classList.remove("noVNC_open"); - }, + closeConnectPanel: function() { + document.getElementById('noVNC_connect_dlg') + .classList.remove("noVNC_open"); + }, - connect: function(event, password) { - var host = UI.getSetting('host'); - var port = UI.getSetting('port'); - var path = UI.getSetting('path'); + connect: function(event, password) { + var host = UI.getSetting('host'); + var port = UI.getSetting('port'); + var path = UI.getSetting('path'); - if (typeof password === 'undefined') { - password = WebUtil.getConfigVar('password'); - } + if (typeof password === 'undefined') { + password = WebUtil.getConfigVar('password'); + } - if (password === null) { - password = undefined; - } + if (password === null) { + password = undefined; + } - if ((!host) || (!port)) { - var msg = _("Must set host and port"); - Util.Error(msg); - UI.showStatus(msg, 'error'); - return; - } + if ((!host) || (!port)) { + var msg = _("Must set host and port"); + Log.Error(msg); + UI.showStatus(msg, 'error'); + return; + } - if (!UI.initRFB()) return; + if (!UI.initRFB()) return; - UI.closeAllPanels(); - UI.closeConnectPanel(); + UI.closeAllPanels(); + UI.closeConnectPanel(); - UI.rfb.set_encrypt(UI.getSetting('encrypt')); - UI.rfb.set_true_color(UI.getSetting('true_color')); - UI.rfb.set_shared(UI.getSetting('shared')); - UI.rfb.set_repeaterID(UI.getSetting('repeaterID')); + UI.rfb.set_encrypt(UI.getSetting('encrypt')); + UI.rfb.set_true_color(UI.getSetting('true_color')); + UI.rfb.set_shared(UI.getSetting('shared')); + UI.rfb.set_repeaterID(UI.getSetting('repeaterID')); - UI.updateLocalCursor(); - UI.updateViewOnly(); + UI.updateLocalCursor(); + UI.updateViewOnly(); - UI.rfb.connect(host, port, password, path); - }, + UI.rfb.connect(host, port, password, path); + }, - disconnect: function() { - UI.closeAllPanels(); - UI.rfb.disconnect(); + disconnect: function() { + UI.closeAllPanels(); + UI.rfb.disconnect(); - // Disable automatic reconnecting - UI.inhibit_reconnect = true; + // Disable automatic reconnecting + UI.inhibit_reconnect = true; - // Restore the callback used for initial resize - UI.rfb.set_onFBUComplete(UI.initialResize); + // Restore the callback used for initial resize + UI.rfb.set_onFBUComplete(UI.initialResize); - // Don't display the connection settings until we're actually disconnected - }, + // Don't display the connection settings until we're actually disconnected + }, - reconnect: function() { + reconnect: function() { + UI.reconnect_callback = null; + + // if reconnect has been disabled in the meantime, do nothing. + if (UI.inhibit_reconnect) { + return; + } + + UI.connect(null, UI.reconnect_password); + }, + + disconnectFinished: function (rfb, reason) { + if (typeof reason !== 'undefined') { + UI.showStatus(reason, 'error'); + } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) { + document.getElementById("noVNC_transition_text").textContent = _("Reconnecting..."); + document.documentElement.classList.add("noVNC_reconnecting"); + + var delay = parseInt(UI.getSetting('reconnect_delay')); + UI.reconnect_callback = setTimeout(UI.reconnect, delay); + return; + } + + UI.openControlbar(); + UI.openConnectPanel(); + }, + + cancelReconnect: function() { + if (UI.reconnect_callback !== null) { + clearTimeout(UI.reconnect_callback); UI.reconnect_callback = null; + } - // if reconnect has been disabled in the meantime, do nothing. - if (UI.inhibit_reconnect) { - return; - } - - UI.connect(null, UI.reconnect_password); - }, - - disconnectFinished: function (rfb, reason) { - if (typeof reason !== 'undefined') { - UI.showStatus(reason, 'error'); - } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) { - document.getElementById("noVNC_transition_text").textContent = _("Reconnecting..."); - document.documentElement.classList.add("noVNC_reconnecting"); - - var delay = parseInt(UI.getSetting('reconnect_delay')); - UI.reconnect_callback = setTimeout(UI.reconnect, delay); - return; - } - - UI.openControlbar(); - UI.openConnectPanel(); - }, - - cancelReconnect: function() { - if (UI.reconnect_callback !== null) { - clearTimeout(UI.reconnect_callback); - UI.reconnect_callback = null; - } - - document.documentElement.classList.remove("noVNC_reconnecting"); - UI.openControlbar(); - UI.openConnectPanel(); - }, + document.documentElement.classList.remove("noVNC_reconnecting"); + UI.openControlbar(); + UI.openConnectPanel(); + }, /* ------^------- * /CONNECTION @@ -1153,31 +1156,31 @@ var UI; * PASSWORD * ------v------*/ - passwordRequired: function(rfb, msg) { + passwordRequired: function(rfb, msg) { - document.getElementById('noVNC_password_dlg') - .classList.add('noVNC_open'); + document.getElementById('noVNC_password_dlg') + .classList.add('noVNC_open'); - setTimeout(function () { - document.getElementById('noVNC_password_input').focus(); - }, 100); + setTimeout(function () { + document.getElementById('noVNC_password_input').focus(); + }, 100); - if (typeof msg === 'undefined') { - msg = _("Password is required"); - } - Util.Warn(msg); - UI.showStatus(msg, "warning"); - }, + if (typeof msg === 'undefined') { + msg = _("Password is required"); + } + Log.Warn(msg); + UI.showStatus(msg, "warning"); + }, - setPassword: function(e) { - var password = document.getElementById('noVNC_password_input').value; - UI.rfb.sendPassword(password); - UI.reconnect_password = password; - document.getElementById('noVNC_password_dlg') - .classList.remove('noVNC_open'); - // Prevent actually submitting the form - e.preventDefault(); - }, + setPassword: function(e) { + var password = document.getElementById('noVNC_password_input').value; + UI.rfb.sendPassword(password); + UI.reconnect_password = password; + document.getElementById('noVNC_password_dlg') + .classList.remove('noVNC_open'); + // Prevent actually submitting the form + e.preventDefault(); + }, /* ------^------- * /PASSWORD @@ -1185,47 +1188,47 @@ var UI; * FULLSCREEN * ------v------*/ - toggleFullscreen: function() { - if (document.fullscreenElement || // alternative standard method - document.mozFullScreenElement || // currently working methods - document.webkitFullscreenElement || - document.msFullscreenElement) { - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } - } else { - if (document.documentElement.requestFullscreen) { - document.documentElement.requestFullscreen(); - } else if (document.documentElement.mozRequestFullScreen) { - document.documentElement.mozRequestFullScreen(); - } else if (document.documentElement.webkitRequestFullscreen) { - document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); - } else if (document.body.msRequestFullscreen) { - document.body.msRequestFullscreen(); - } + toggleFullscreen: function() { + if (document.fullscreenElement || // alternative standard method + document.mozFullScreenElement || // currently working methods + document.webkitFullscreenElement || + document.msFullscreenElement) { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); } - UI.enableDisableViewClip(); - UI.updateFullscreenButton(); - }, + } else { + if (document.documentElement.requestFullscreen) { + document.documentElement.requestFullscreen(); + } else if (document.documentElement.mozRequestFullScreen) { + document.documentElement.mozRequestFullScreen(); + } else if (document.documentElement.webkitRequestFullscreen) { + document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (document.body.msRequestFullscreen) { + document.body.msRequestFullscreen(); + } + } + UI.enableDisableViewClip(); + UI.updateFullscreenButton(); + }, - updateFullscreenButton: function() { - if (document.fullscreenElement || // alternative standard method - document.mozFullScreenElement || // currently working methods - document.webkitFullscreenElement || - document.msFullscreenElement ) { - document.getElementById('noVNC_fullscreen_button') - .classList.add("noVNC_selected"); - } else { - document.getElementById('noVNC_fullscreen_button') - .classList.remove("noVNC_selected"); - } - }, + updateFullscreenButton: function() { + if (document.fullscreenElement || // alternative standard method + document.mozFullScreenElement || // currently working methods + document.webkitFullscreenElement || + document.msFullscreenElement ) { + document.getElementById('noVNC_fullscreen_button') + .classList.add("noVNC_selected"); + } else { + document.getElementById('noVNC_fullscreen_button') + .classList.remove("noVNC_selected"); + } + }, /* ------^------- * /FULLSCREEN @@ -1233,62 +1236,62 @@ var UI; * RESIZE * ------v------*/ - // Apply remote resizing or local scaling - applyResizeMode: function() { - if (!UI.rfb) return; + // Apply remote resizing or local scaling + applyResizeMode: function() { + if (!UI.rfb) return; - var screen = UI.screenSize(); + var screen = UI.screenSize(); - if (screen && UI.connected && UI.rfb.get_display()) { + if (screen && UI.connected && UI.rfb.get_display()) { - var display = UI.rfb.get_display(); - var resizeMode = UI.getSetting('resize'); - display.set_scale(1); + var display = UI.rfb.get_display(); + var resizeMode = UI.getSetting('resize'); + display.set_scale(1); - // Make sure the viewport is adjusted first - UI.updateViewClip(); + // Make sure the viewport is adjusted first + UI.updateViewClip(); - if (resizeMode === 'remote') { + if (resizeMode === 'remote') { - // Request changing the resolution of the remote display to - // the size of the local browser viewport. + // Request changing the resolution of the remote display to + // the size of the local browser viewport. - // In order to not send multiple requests before the browser-resize - // is finished we wait 0.5 seconds before sending the request. - clearTimeout(UI.resizeTimeout); - UI.resizeTimeout = setTimeout(function(){ - // Request a remote size covering the viewport - if (UI.rfb.requestDesktopSize(screen.w, screen.h)) { - Util.Debug('Requested new desktop size: ' + - screen.w + 'x' + screen.h); - } - }, 500); + // In order to not send multiple requests before the browser-resize + // is finished we wait 0.5 seconds before sending the request. + clearTimeout(UI.resizeTimeout); + UI.resizeTimeout = setTimeout(function(){ + // Request a remote size covering the viewport + if (UI.rfb.requestDesktopSize(screen.w, screen.h)) { + Log.Debug('Requested new desktop size: ' + + screen.w + 'x' + screen.h); + } + }, 500); - } else if (resizeMode === 'scale' || resizeMode === 'downscale') { - var downscaleOnly = resizeMode === 'downscale'; - display.autoscale(screen.w, screen.h, downscaleOnly); - UI.fixScrollbars(); - } + } else if (resizeMode === 'scale' || resizeMode === 'downscale') { + var downscaleOnly = resizeMode === 'downscale'; + display.autoscale(screen.w, screen.h, downscaleOnly); + UI.fixScrollbars(); } - }, + } + }, - // Gets the the size of the available viewport in the browser window - screenSize: function() { - var screen = document.getElementById('noVNC_screen'); - return {w: screen.offsetWidth, h: screen.offsetHeight}; - }, + // Gets the the size of the available viewport in the browser window + screenSize: function() { + var screen = document.getElementById('noVNC_screen'); + return {w: screen.offsetWidth, h: screen.offsetHeight}; + }, - // Normally we only apply the current resize mode after a window resize - // event. This means that when a new connection is opened, there is no - // resize mode active. - // We have to wait until the first FBU because this is where the client - // will find the supported encodings of the server. Some calls later in - // the chain is dependant on knowing the server-capabilities. - initialResize: function(rfb, fbu) { - UI.applyResizeMode(); - // After doing this once, we remove the callback. - UI.rfb.set_onFBUComplete(function() { }); - }, + // Normally we only apply the current resize mode after a window resize + // event. This means that when a new connection is opened, there is no + // resize mode active. + // We have to wait until the first FBU because this is where the client + // will find the supported encodings of the server. Some calls later in + // the chain is dependant on knowing the server-capabilities. + initialResize: function(rfb, fbu) { + UI.applyResizeMode(); + // After doing this once, we remove the callback. + UI.rfb.set_onFBUComplete(function() { }); + }, /* ------^------- * /RESIZE @@ -1296,58 +1299,58 @@ var UI; * CLIPPING * ------v------*/ - // Set and configure viewport clipping - setViewClip: function(clip) { - UI.updateSetting('clip', clip); - UI.updateViewClip(); - }, + // Set and configure viewport clipping + setViewClip: function(clip) { + UI.updateSetting('clip', clip); + UI.updateViewClip(); + }, - // Update parameters that depend on the clip setting - updateViewClip: function() { - if (!UI.rfb) return; + // Update parameters that depend on the clip setting + updateViewClip: function() { + if (!UI.rfb) return; - var display = UI.rfb.get_display(); - var cur_clip = display.get_viewport(); - var new_clip = UI.getSetting('clip'); + var display = UI.rfb.get_display(); + var cur_clip = display.get_viewport(); + var new_clip = UI.getSetting('clip'); - var resizeSetting = UI.getSetting('resize'); - if (resizeSetting === 'downscale' || resizeSetting === 'scale') { - // Disable clipping if we are scaling - new_clip = false; - } else if (Util.isTouchDevice) { - // Touch devices usually have shit scrollbars - new_clip = true; - } + var resizeSetting = UI.getSetting('resize'); + if (resizeSetting === 'downscale' || resizeSetting === 'scale') { + // Disable clipping if we are scaling + new_clip = false; + } else if (isTouchDevice) { + // Touch devices usually have shit scrollbars + new_clip = true; + } - if (cur_clip !== new_clip) { - display.set_viewport(new_clip); - } + if (cur_clip !== new_clip) { + display.set_viewport(new_clip); + } - var size = UI.screenSize(); + var size = UI.screenSize(); - if (new_clip && size) { - // When clipping is enabled, the screen is limited to - // the size of the browser window. - display.viewportChangeSize(size.w, size.h); - UI.fixScrollbars(); - } + if (new_clip && size) { + // When clipping is enabled, the screen is limited to + // the size of the browser window. + display.viewportChangeSize(size.w, size.h); + UI.fixScrollbars(); + } - // Changing the viewport may change the state of - // the dragging button - UI.updateViewDrag(); - }, + // Changing the viewport may change the state of + // the dragging button + UI.updateViewDrag(); + }, - // Handle special cases where clipping is forced on/off or locked - enableDisableViewClip: function() { - var resizeSetting = UI.getSetting('resize'); - // Disable clipping if we are scaling, connected or on touch - if (resizeSetting === 'downscale' || resizeSetting === 'scale' || - Util.isTouchDevice) { - UI.disableSetting('clip'); - } else { - UI.enableSetting('clip'); - } - }, + // Handle special cases where clipping is forced on/off or locked + enableDisableViewClip: function() { + var resizeSetting = UI.getSetting('resize'); + // Disable clipping if we are scaling, connected or on touch + if (resizeSetting === 'downscale' || resizeSetting === 'scale' || + isTouchDevice) { + UI.disableSetting('clip'); + } else { + UI.enableSetting('clip'); + } + }, /* ------^------- * /CLIPPING @@ -1355,70 +1358,70 @@ var UI; * VIEWDRAG * ------v------*/ - toggleViewDrag: function() { - if (!UI.rfb) return; + toggleViewDrag: function() { + if (!UI.rfb) return; - var drag = UI.rfb.get_viewportDrag(); - UI.setViewDrag(!drag); - }, + var drag = UI.rfb.get_viewportDrag(); + UI.setViewDrag(!drag); + }, - // Set the view drag mode which moves the viewport on mouse drags - setViewDrag: function(drag) { - if (!UI.rfb) return; + // Set the view drag mode which moves the viewport on mouse drags + setViewDrag: function(drag) { + if (!UI.rfb) return; - UI.rfb.set_viewportDrag(drag); + UI.rfb.set_viewportDrag(drag); - UI.updateViewDrag(); - }, + UI.updateViewDrag(); + }, - updateViewDrag: function() { - var clipping = false; + updateViewDrag: function() { + var clipping = false; - if (!UI.connected) return; + if (!UI.connected) return; - // Check if viewport drag is possible. It is only possible - // if the remote display is clipping the client display. - if (UI.rfb.get_display().get_viewport() && - UI.rfb.get_display().clippingDisplay()) { - clipping = true; - } + // Check if viewport drag is possible. It is only possible + // if the remote display is clipping the client display. + if (UI.rfb.get_display().get_viewport() && + UI.rfb.get_display().clippingDisplay()) { + clipping = true; + } - var viewDragButton = document.getElementById('noVNC_view_drag_button'); + var viewDragButton = document.getElementById('noVNC_view_drag_button'); - if (!clipping && - UI.rfb.get_viewportDrag()) { - // The size of the remote display is the same or smaller - // than the client display. Make sure viewport drag isn't - // active when it can't be used. - UI.rfb.set_viewportDrag(false); - } + if (!clipping && + UI.rfb.get_viewportDrag()) { + // The size of the remote display is the same or smaller + // than the client display. Make sure viewport drag isn't + // active when it can't be used. + UI.rfb.set_viewportDrag(false); + } - if (UI.rfb.get_viewportDrag()) { - viewDragButton.classList.add("noVNC_selected"); - } else { - viewDragButton.classList.remove("noVNC_selected"); - } + if (UI.rfb.get_viewportDrag()) { + viewDragButton.classList.add("noVNC_selected"); + } else { + viewDragButton.classList.remove("noVNC_selected"); + } - // Different behaviour for touch vs non-touch - // The button is disabled instead of hidden on touch devices - if (Util.isTouchDevice) { - viewDragButton.classList.remove("noVNC_hidden"); + // Different behaviour for touch vs non-touch + // The button is disabled instead of hidden on touch devices + if (isTouchDevice) { + viewDragButton.classList.remove("noVNC_hidden"); - if (clipping) { - viewDragButton.disabled = false; - } else { - viewDragButton.disabled = true; - } - } else { + if (clipping) { viewDragButton.disabled = false; - - if (clipping) { - viewDragButton.classList.remove("noVNC_hidden"); - } else { - viewDragButton.classList.add("noVNC_hidden"); - } + } else { + viewDragButton.disabled = true; } - }, + } else { + viewDragButton.disabled = false; + + if (clipping) { + viewDragButton.classList.remove("noVNC_hidden"); + } else { + viewDragButton.classList.add("noVNC_hidden"); + } + } + }, /* ------^------- * /VIEWDRAG @@ -1426,155 +1429,155 @@ var UI; * KEYBOARD * ------v------*/ - showVirtualKeyboard: function() { - if (!Util.isTouchDevice) return; + showVirtualKeyboard: function() { + if (!isTouchDevice) return; - var input = document.getElementById('noVNC_keyboardinput'); + var input = document.getElementById('noVNC_keyboardinput'); - if (document.activeElement == input) return; + if (document.activeElement == input) return; - input.focus(); + input.focus(); - try { - var l = input.value.length; - // Move the caret to the end - input.setSelectionRange(l, l); - } catch (err) {} // setSelectionRange is undefined in Google Chrome - }, + try { + var l = input.value.length; + // Move the caret to the end + input.setSelectionRange(l, l); + } catch (err) {} // setSelectionRange is undefined in Google Chrome + }, - hideVirtualKeyboard: function() { - if (!Util.isTouchDevice) return; + hideVirtualKeyboard: function() { + if (!isTouchDevice) return; - var input = document.getElementById('noVNC_keyboardinput'); + var input = document.getElementById('noVNC_keyboardinput'); - if (document.activeElement != input) return; + if (document.activeElement != input) return; - input.blur(); - }, + input.blur(); + }, - toggleVirtualKeyboard: function () { - if (document.getElementById('noVNC_keyboard_button') - .classList.contains("noVNC_selected")) { - UI.hideVirtualKeyboard(); - } else { - UI.showVirtualKeyboard(); + toggleVirtualKeyboard: function () { + if (document.getElementById('noVNC_keyboard_button') + .classList.contains("noVNC_selected")) { + UI.hideVirtualKeyboard(); + } else { + UI.showVirtualKeyboard(); + } + }, + + onfocusVirtualKeyboard: function(event) { + document.getElementById('noVNC_keyboard_button') + .classList.add("noVNC_selected"); + }, + + onblurVirtualKeyboard: function(event) { + document.getElementById('noVNC_keyboard_button') + .classList.remove("noVNC_selected"); + }, + + keepVirtualKeyboard: function(event) { + var input = document.getElementById('noVNC_keyboardinput'); + + // Only prevent focus change if the virtual keyboard is active + if (document.activeElement != input) { + return; + } + + // Only allow focus to move to other elements that need + // focus to function properly + if (event.target.form !== undefined) { + switch (event.target.type) { + case 'text': + case 'email': + case 'search': + case 'password': + case 'tel': + case 'url': + case 'textarea': + case 'select-one': + case 'select-multiple': + return; } - }, + } - onfocusVirtualKeyboard: function(event) { - document.getElementById('noVNC_keyboard_button') - .classList.add("noVNC_selected"); - }, + event.preventDefault(); + }, - onblurVirtualKeyboard: function(event) { - document.getElementById('noVNC_keyboard_button') - .classList.remove("noVNC_selected"); - }, + keyboardinputReset: function() { + var kbi = document.getElementById('noVNC_keyboardinput'); + kbi.value = new Array(UI.defaultKeyboardinputLen).join("_"); + UI.lastKeyboardinput = kbi.value; + }, - keepVirtualKeyboard: function(event) { - var input = document.getElementById('noVNC_keyboardinput'); + // When normal keyboard events are left uncought, use the input events from + // the keyboardinput element instead and generate the corresponding key events. + // This code is required since some browsers on Android are inconsistent in + // sending keyCodes in the normal keyboard events when using on screen keyboards. + keyInput: function(event) { - // Only prevent focus change if the virtual keyboard is active - if (document.activeElement != input) { - return; + if (!UI.rfb) return; + + var newValue = event.target.value; + + if (!UI.lastKeyboardinput) { + UI.keyboardinputReset(); + } + var oldValue = UI.lastKeyboardinput; + + var newLen; + try { + // Try to check caret position since whitespace at the end + // will not be considered by value.length in some browsers + newLen = Math.max(event.target.selectionStart, newValue.length); + } catch (err) { + // selectionStart is undefined in Google Chrome + newLen = newValue.length; + } + var oldLen = oldValue.length; + + var backspaces; + var inputs = newLen - oldLen; + if (inputs < 0) { + backspaces = -inputs; + } else { + backspaces = 0; + } + + // Compare the old string with the new to account for + // text-corrections or other input that modify existing text + var i; + for (i = 0; i < Math.min(oldLen, newLen); i++) { + if (newValue.charAt(i) != oldValue.charAt(i)) { + inputs = newLen - i; + backspaces = oldLen - i; + break; } + } - // Only allow focus to move to other elements that need - // focus to function properly - if (event.target.form !== undefined) { - switch (event.target.type) { - case 'text': - case 'email': - case 'search': - case 'password': - case 'tel': - case 'url': - case 'textarea': - case 'select-one': - case 'select-multiple': - return; - } - } + // Send the key events + for (i = 0; i < backspaces; i++) { + UI.rfb.sendKey(KeyTable.XK_BackSpace); + } + for (i = newLen - inputs; i < newLen; i++) { + UI.rfb.sendKey(keysyms.fromUnicode(newValue.charCodeAt(i)).keysym); + } - event.preventDefault(); - }, - - keyboardinputReset: function() { - var kbi = document.getElementById('noVNC_keyboardinput'); - kbi.value = new Array(UI.defaultKeyboardinputLen).join("_"); - UI.lastKeyboardinput = kbi.value; - }, - - // When normal keyboard events are left uncought, use the input events from - // the keyboardinput element instead and generate the corresponding key events. - // This code is required since some browsers on Android are inconsistent in - // sending keyCodes in the normal keyboard events when using on screen keyboards. - keyInput: function(event) { - - if (!UI.rfb) return; - - var newValue = event.target.value; - - if (!UI.lastKeyboardinput) { - UI.keyboardinputReset(); - } - var oldValue = UI.lastKeyboardinput; - - var newLen; - try { - // Try to check caret position since whitespace at the end - // will not be considered by value.length in some browsers - newLen = Math.max(event.target.selectionStart, newValue.length); - } catch (err) { - // selectionStart is undefined in Google Chrome - newLen = newValue.length; - } - var oldLen = oldValue.length; - - var backspaces; - var inputs = newLen - oldLen; - if (inputs < 0) { - backspaces = -inputs; - } else { - backspaces = 0; - } - - // Compare the old string with the new to account for - // text-corrections or other input that modify existing text - var i; - for (i = 0; i < Math.min(oldLen, newLen); i++) { - if (newValue.charAt(i) != oldValue.charAt(i)) { - inputs = newLen - i; - backspaces = oldLen - i; - break; - } - } - - // Send the key events - for (i = 0; i < backspaces; i++) { - UI.rfb.sendKey(KeyTable.XK_BackSpace); - } - for (i = newLen - inputs; i < newLen; i++) { - UI.rfb.sendKey(keysyms.fromUnicode(newValue.charCodeAt(i)).keysym); - } - - // Control the text content length in the keyboardinput element - if (newLen > 2 * UI.defaultKeyboardinputLen) { - UI.keyboardinputReset(); - } else if (newLen < 1) { - // There always have to be some text in the keyboardinput - // element with which backspace can interact. - UI.keyboardinputReset(); - // This sometimes causes the keyboard to disappear for a second - // but it is required for the android keyboard to recognize that - // text has been added to the field - event.target.blur(); - // This has to be ran outside of the input handler in order to work - setTimeout(event.target.focus.bind(event.target), 0); - } else { - UI.lastKeyboardinput = newValue; - } - }, + // Control the text content length in the keyboardinput element + if (newLen > 2 * UI.defaultKeyboardinputLen) { + UI.keyboardinputReset(); + } else if (newLen < 1) { + // There always have to be some text in the keyboardinput + // element with which backspace can interact. + UI.keyboardinputReset(); + // This sometimes causes the keyboard to disappear for a second + // but it is required for the android keyboard to recognize that + // text has been added to the field + event.target.blur(); + // This has to be ran outside of the input handler in order to work + setTimeout(event.target.focus.bind(event.target), 0); + } else { + UI.lastKeyboardinput = newValue; + } + }, /* ------^------- * /KEYBOARD @@ -1582,65 +1585,65 @@ var UI; * EXTRA KEYS * ------v------*/ - openExtraKeys: function() { - UI.closeAllPanels(); - UI.openControlbar(); + openExtraKeys: function() { + UI.closeAllPanels(); + UI.openControlbar(); - document.getElementById('noVNC_modifiers') - .classList.add("noVNC_open"); - document.getElementById('noVNC_toggle_extra_keys_button') - .classList.add("noVNC_selected"); - }, + document.getElementById('noVNC_modifiers') + .classList.add("noVNC_open"); + document.getElementById('noVNC_toggle_extra_keys_button') + .classList.add("noVNC_selected"); + }, - closeExtraKeys: function() { - document.getElementById('noVNC_modifiers') - .classList.remove("noVNC_open"); - document.getElementById('noVNC_toggle_extra_keys_button') - .classList.remove("noVNC_selected"); - }, + closeExtraKeys: function() { + document.getElementById('noVNC_modifiers') + .classList.remove("noVNC_open"); + document.getElementById('noVNC_toggle_extra_keys_button') + .classList.remove("noVNC_selected"); + }, - toggleExtraKeys: function() { - if(document.getElementById('noVNC_modifiers') - .classList.contains("noVNC_open")) { - UI.closeExtraKeys(); - } else { - UI.openExtraKeys(); - } - }, + toggleExtraKeys: function() { + if(document.getElementById('noVNC_modifiers') + .classList.contains("noVNC_open")) { + UI.closeExtraKeys(); + } else { + UI.openExtraKeys(); + } + }, - sendEsc: function() { - UI.rfb.sendKey(KeyTable.XK_Escape); - }, + sendEsc: function() { + UI.rfb.sendKey(KeyTable.XK_Escape); + }, - sendTab: function() { - UI.rfb.sendKey(KeyTable.XK_Tab); - }, + sendTab: function() { + UI.rfb.sendKey(KeyTable.XK_Tab); + }, - toggleCtrl: function() { - var btn = document.getElementById('noVNC_toggle_ctrl_button'); - if (btn.classList.contains("noVNC_selected")) { - UI.rfb.sendKey(KeyTable.XK_Control_L, false); - btn.classList.remove("noVNC_selected"); - } else { - UI.rfb.sendKey(KeyTable.XK_Control_L, true); - btn.classList.add("noVNC_selected"); - } - }, + toggleCtrl: function() { + var btn = document.getElementById('noVNC_toggle_ctrl_button'); + if (btn.classList.contains("noVNC_selected")) { + UI.rfb.sendKey(KeyTable.XK_Control_L, false); + btn.classList.remove("noVNC_selected"); + } else { + UI.rfb.sendKey(KeyTable.XK_Control_L, true); + btn.classList.add("noVNC_selected"); + } + }, - toggleAlt: function() { - var btn = document.getElementById('noVNC_toggle_alt_button'); - if (btn.classList.contains("noVNC_selected")) { - UI.rfb.sendKey(KeyTable.XK_Alt_L, false); - btn.classList.remove("noVNC_selected"); - } else { - UI.rfb.sendKey(KeyTable.XK_Alt_L, true); - btn.classList.add("noVNC_selected"); - } - }, + toggleAlt: function() { + var btn = document.getElementById('noVNC_toggle_alt_button'); + if (btn.classList.contains("noVNC_selected")) { + UI.rfb.sendKey(KeyTable.XK_Alt_L, false); + btn.classList.remove("noVNC_selected"); + } else { + UI.rfb.sendKey(KeyTable.XK_Alt_L, true); + btn.classList.add("noVNC_selected"); + } + }, - sendCtrlAltDel: function() { - UI.rfb.sendCtrlAltDel(); - }, + sendCtrlAltDel: function() { + UI.rfb.sendCtrlAltDel(); + }, /* ------^------- * /EXTRA KEYS @@ -1648,108 +1651,108 @@ var UI; * MISC * ------v------*/ - setMouseButton: function(num) { - var view_only = UI.rfb.get_view_only(); - if (UI.rfb && !view_only) { - UI.rfb.get_mouse().set_touchButton(num); + setMouseButton: function(num) { + var view_only = UI.rfb.get_view_only(); + if (UI.rfb && !view_only) { + UI.rfb.get_mouse().set_touchButton(num); + } + + var blist = [0, 1,2,4]; + for (var b = 0; b < blist.length; b++) { + var button = document.getElementById('noVNC_mouse_button' + + blist[b]); + if (blist[b] === num && !view_only) { + button.classList.remove("noVNC_hidden"); + } else { + button.classList.add("noVNC_hidden"); } + } + }, - var blist = [0, 1,2,4]; - for (var b = 0; b < blist.length; b++) { - var button = document.getElementById('noVNC_mouse_button' + - blist[b]); - if (blist[b] === num && !view_only) { - button.classList.remove("noVNC_hidden"); - } else { - button.classList.add("noVNC_hidden"); - } - } - }, + displayBlur: function() { + if (UI.rfb && !UI.rfb.get_view_only()) { + UI.rfb.get_keyboard().set_focused(false); + UI.rfb.get_mouse().set_focused(false); + } + }, - displayBlur: function() { - if (UI.rfb && !UI.rfb.get_view_only()) { - UI.rfb.get_keyboard().set_focused(false); - UI.rfb.get_mouse().set_focused(false); - } - }, + displayFocus: function() { + if (UI.rfb && !UI.rfb.get_view_only()) { + UI.rfb.get_keyboard().set_focused(true); + UI.rfb.get_mouse().set_focused(true); + } + }, - displayFocus: function() { - if (UI.rfb && !UI.rfb.get_view_only()) { - UI.rfb.get_keyboard().set_focused(true); - UI.rfb.get_mouse().set_focused(true); - } - }, + updateLocalCursor: function() { + UI.rfb.set_local_cursor(UI.getSetting('cursor')); + }, - updateLocalCursor: function() { - UI.rfb.set_local_cursor(UI.getSetting('cursor')); - }, + updateViewOnly: function() { + UI.rfb.set_view_only(UI.getSetting('view_only')); + }, - updateViewOnly: function() { - UI.rfb.set_view_only(UI.getSetting('view_only')); - }, + updateLogging: function() { + WebUtil.init_logging(UI.getSetting('logging')); + }, - updateLogging: function() { - WebUtil.init_logging(UI.getSetting('logging')); - }, + updateSessionSize: function(rfb, width, height) { + UI.updateViewClip(); + UI.fixScrollbars(); + }, - updateSessionSize: function(rfb, width, height) { - UI.updateViewClip(); - UI.fixScrollbars(); - }, + fixScrollbars: function() { + // This is a hack because Chrome screws up the calculation + // for when scrollbars are needed. So to fix it we temporarily + // toggle them off and on. + var screen = document.getElementById('noVNC_screen'); + screen.style.overflow = 'hidden'; + // Force Chrome to recalculate the layout by asking for + // an element's dimensions + screen.getBoundingClientRect(); + screen.style.overflow = null; + }, - fixScrollbars: function() { - // This is a hack because Chrome screws up the calculation - // for when scrollbars are needed. So to fix it we temporarily - // toggle them off and on. - var screen = document.getElementById('noVNC_screen'); - screen.style.overflow = 'hidden'; - // Force Chrome to recalculate the layout by asking for - // an element's dimensions - screen.getBoundingClientRect(); - screen.style.overflow = null; - }, + updateDesktopName: function(rfb, name) { + UI.desktopName = name; + // Display the desktop name in the document title + document.title = name + " - noVNC"; + }, - updateDesktopName: function(rfb, name) { - UI.desktopName = name; - // Display the desktop name in the document title - document.title = name + " - noVNC"; - }, + bell: function(rfb) { + if (WebUtil.getConfigVar('bell', 'on') === 'on') { + document.getElementById('noVNC_bell').play(); + } + }, - bell: function(rfb) { - if (WebUtil.getConfigVar('bell', 'on') === 'on') { - document.getElementById('noVNC_bell').play(); - } - }, - - //Helper to add options to dropdown. - addOption: function(selectbox, text, value) { - var optn = document.createElement("OPTION"); - optn.text = text; - optn.value = value; - selectbox.options.add(optn); - }, + //Helper to add options to dropdown. + addOption: function(selectbox, text, value) { + var optn = document.createElement("OPTION"); + optn.text = text; + optn.value = value; + selectbox.options.add(optn); + }, /* ------^------- * /MISC * ============== */ - }; +}; - // Set up translations - var LINGUAS = ["de", "el", "nl", "sv"]; - Util.Localisation.setup(LINGUAS); - if (Util.Localisation.language !== "en" && Util.Localisation.dictionary === undefined) { - WebUtil.fetchJSON('app/locale/' + Util.Localisation.language + '.json', function (translations) { - Util.Localisation.dictionary = translations; +// Set up translations +var LINGUAS = ["de", "el", "nl", "sv"]; +l10n.setup(LINGUAS); +if (l10n.language !== "en" && l10n.dictionary === undefined) { + WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) { + l10n.dictionary = translations; + + // wait for translations to load before loading the UI + UI.prime(); + }, function (err) { + throw err; + }); +} else { + UI.prime(); +} - // wait for translations to load before loading the UI - UI.load(); - }, function (err) { - throw err; - }); - } else { - UI.load(); - } -})(); export default UI; diff --git a/app/webutil.js b/app/webutil.js index 2ce1b2d3..7cee466c 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -10,31 +10,21 @@ /*jslint bitwise: false, white: false, browser: true, devel: true */ /*global Util, window, document */ -import Util from "../core/util.js"; - -// Globals defined here -var WebUtil = {}; - -/* - * ------------------------------------------------------ - * Namespaced in WebUtil - * ------------------------------------------------------ - */ +import { init_logging as main_init_logging } from '../core/util/logging.js'; // init log level reading the logging HTTP param -WebUtil.init_logging = function (level) { +export function init_logging (level) { "use strict"; if (typeof level !== "undefined") { - Util._log_level = level; + main_init_logging(level); } else { var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/); - Util._log_level = (param || ['', Util._log_level])[1]; + main_init_logging(param || undefined); } - Util.init_logging(); }; // Read a query string variable -WebUtil.getQueryVar = function (name, defVal) { +export function getQueryVar (name, defVal) { "use strict"; var re = new RegExp('.*[?&]' + name + '=([^&#]*)'), match = document.location.href.match(re); @@ -47,7 +37,7 @@ WebUtil.getQueryVar = function (name, defVal) { }; // Read a hash fragment variable -WebUtil.getHashVar = function (name, defVal) { +export function getHashVar (name, defVal) { "use strict"; var re = new RegExp('.*[&#]' + name + '=([^&]*)'), match = document.location.hash.match(re); @@ -61,11 +51,11 @@ WebUtil.getHashVar = function (name, defVal) { // Read a variable from the fragment or the query string // Fragment takes precedence -WebUtil.getConfigVar = function (name, defVal) { +export function getConfigVar (name, defVal) { "use strict"; - var val = WebUtil.getHashVar(name); + var val = getHashVar(name); if (val === null) { - val = WebUtil.getQueryVar(name, defVal); + val = getQueryVar(name, defVal); } return val; }; @@ -75,7 +65,7 @@ WebUtil.getConfigVar = function (name, defVal) { */ // No days means only for this browser session -WebUtil.createCookie = function (name, value, days) { +export function createCookie (name, value, days) { "use strict"; var date, expires; if (days) { @@ -95,7 +85,7 @@ WebUtil.createCookie = function (name, value, days) { document.cookie = name + "=" + value + expires + "; path=/" + secure; }; -WebUtil.readCookie = function (name, defaultValue) { +export function readCookie (name, defaultValue) { "use strict"; var nameEQ = name + "=", ca = document.cookie.split(';'); @@ -108,22 +98,24 @@ WebUtil.readCookie = function (name, defaultValue) { return (typeof defaultValue !== 'undefined') ? defaultValue : null; }; -WebUtil.eraseCookie = function (name) { +export function eraseCookie (name) { "use strict"; - WebUtil.createCookie(name, "", -1); + createCookie(name, "", -1); }; /* * Setting handling. */ -WebUtil.initSettings = function (callback /*, ...callbackArgs */) { +var settings = {}; + +export function initSettings (callback /*, ...callbackArgs */) { "use strict"; var callbackArgs = Array.prototype.slice.call(arguments, 1); if (window.chrome && window.chrome.storage) { window.chrome.storage.sync.get(function (cfg) { - WebUtil.settings = cfg; - console.log(WebUtil.settings); + settings = cfg; + console.log(settings); if (callback) { callback.apply(this, callbackArgs); } @@ -137,24 +129,24 @@ WebUtil.initSettings = function (callback /*, ...callbackArgs */) { }; // No days means only for this browser session -WebUtil.writeSetting = function (name, value) { +export function writeSetting (name, value) { "use strict"; if (window.chrome && window.chrome.storage) { //console.log("writeSetting:", name, value); - if (WebUtil.settings[name] !== value) { - WebUtil.settings[name] = value; - window.chrome.storage.sync.set(WebUtil.settings); + if (settings[name] !== value) { + settings[name] = value; + window.chrome.storage.sync.set(settings); } } else { localStorage.setItem(name, value); } }; -WebUtil.readSetting = function (name, defaultValue) { +export function readSetting (name, defaultValue) { "use strict"; var value; if (window.chrome && window.chrome.storage) { - value = WebUtil.settings[name]; + value = settings[name]; } else { value = localStorage.getItem(name); } @@ -168,17 +160,17 @@ WebUtil.readSetting = function (name, defaultValue) { } }; -WebUtil.eraseSetting = function (name) { +export function eraseSetting (name) { "use strict"; if (window.chrome && window.chrome.storage) { window.chrome.storage.sync.remove(name); - delete WebUtil.settings[name]; + delete settings[name]; } else { localStorage.removeItem(name); } }; -WebUtil.injectParamIfMissing = function (path, param, value) { +export function injectParamIfMissing (path, param, value) { // force pretend that we're dealing with a relative path // (assume that we wanted an extra if we pass one in) path = "/" + path; @@ -212,7 +204,7 @@ WebUtil.injectParamIfMissing = function (path, param, value) { // IE11 support or polyfill promises and fetch in IE11. // resolve will receive an object on success, while reject // will receive either an event or an error on failure. -WebUtil.fetchJSON = function (path, resolve, reject) { +export function fetchJSON(path, resolve, reject) { // NB: IE11 doesn't support JSON as a responseType const req = new XMLHttpRequest(); req.open('GET', path); @@ -240,6 +232,4 @@ WebUtil.fetchJSON = function (path, resolve, reject) { }; req.send(); -}; - -export default WebUtil; +} diff --git a/core/base64.js b/core/base64.js index 3b9ebe54..9d24a25f 100644 --- a/core/base64.js +++ b/core/base64.js @@ -7,7 +7,7 @@ /*jslint white: false */ /*global console */ -var Base64 = { +export default { /* Convert data (an array of integers) to a Base64 string. */ toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''), base64Pad : '=', @@ -15,7 +15,7 @@ var Base64 = { encode: function (data) { "use strict"; var result = ''; - var toBase64Table = Base64.toBase64Table; + var toBase64Table = this.toBase64Table; var length = data.length; var lengthpad = (length % 3); // Convert every three bytes to 4 ascii characters. @@ -63,8 +63,8 @@ var Base64 = { decode: function (data, offset) { "use strict"; offset = typeof(offset) !== 'undefined' ? offset : 0; - var toBinaryTable = Base64.toBinaryTable; - var base64Pad = Base64.base64Pad; + var toBinaryTable = this.toBinaryTable; + var base64Pad = this.base64Pad; var result, result_length; var leftbits = 0; // number of bits decoded, but yet to be appended var leftdata = 0; // bits decoded, but yet to be appended @@ -111,5 +111,3 @@ var Base64 = { return result; } }; /* End of Base64 namespace */ - -export default Base64; diff --git a/core/display.js b/core/display.js index a1dac7a4..818c911d 100644 --- a/core/display.js +++ b/core/display.js @@ -10,10 +10,11 @@ /*jslint browser: true, white: false */ /*global Util, Base64, changeCursor */ -import Util from "./util.js"; +import { Engine, browserSupportsCursorURIs as cursorURIsSupported } from './util/browsers.js'; +import { set_defaults, make_properties } from './util/properties.js'; +import * as Log from './util/logging.js'; import Base64 from "./base64.js"; - export default function Display(defaults) { this._drawCtx = null; this._c_forceCanvas = false; @@ -31,7 +32,7 @@ export default function Display(defaults) { this._tile_x = 0; this._tile_y = 0; - Util.set_defaults(this, defaults, { + set_defaults(this, defaults, { 'true_color': true, 'colourMap': [], 'scale': 1.0, @@ -40,7 +41,7 @@ export default function Display(defaults) { "onFlush": function () {}, }); - Util.Debug(">> Display.constructor"); + Log.Debug(">> Display.constructor"); // The visible canvas if (!this._target) { @@ -68,11 +69,11 @@ export default function Display(defaults) { right: this._backbuffer.width, bottom: this._backbuffer.height }; - Util.Debug("User Agent: " + navigator.userAgent); - if (Util.Engine.gecko) { Util.Debug("Browser: gecko " + Util.Engine.gecko); } - if (Util.Engine.webkit) { Util.Debug("Browser: webkit " + Util.Engine.webkit); } - if (Util.Engine.trident) { Util.Debug("Browser: trident " + Util.Engine.trident); } - if (Util.Engine.presto) { Util.Debug("Browser: presto " + Util.Engine.presto); } + Log.Debug("User Agent: " + navigator.userAgent); + if (Engine.gecko) { Log.Debug("Browser: gecko " + Engine.gecko); } + if (Engine.webkit) { Log.Debug("Browser: webkit " + Engine.webkit); } + if (Engine.trident) { Log.Debug("Browser: trident " + Engine.trident); } + if (Engine.presto) { Log.Debug("Browser: presto " + Engine.presto); } this.clear(); @@ -84,788 +85,783 @@ export default function Display(defaults) { } if (this._prefer_js === null) { - Util.Info("Prefering javascript operations"); + Log.Info("Prefering javascript operations"); this._prefer_js = true; } // Determine browser support for setting the cursor via data URI scheme if (this._cursor_uri || this._cursor_uri === null || this._cursor_uri === undefined) { - this._cursor_uri = Util.browserSupportsCursorURIs(); + this._cursor_uri = cursorURIsSupported(); } - Util.Debug("<< Display.constructor"); + Log.Debug("<< Display.constructor"); }; -(function () { - "use strict"; +var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false; +try { + new ImageData(new Uint8ClampedArray(4), 1, 1); + SUPPORTS_IMAGEDATA_CONSTRUCTOR = true; +} catch (ex) { + // ignore failure +} - var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false; - try { - new ImageData(new Uint8ClampedArray(4), 1, 1); - SUPPORTS_IMAGEDATA_CONSTRUCTOR = true; - } catch (ex) { - // ignore failure - } +Display.prototype = { + // Public methods + viewportChangePos: function (deltaX, deltaY) { + var vp = this._viewportLoc; + deltaX = Math.floor(deltaX); + deltaY = Math.floor(deltaY); + if (!this._viewport) { + deltaX = -vp.w; // clamped later of out of bounds + deltaY = -vp.h; + } - Display.prototype = { - // Public methods - viewportChangePos: function (deltaX, deltaY) { - var vp = this._viewportLoc; - deltaX = Math.floor(deltaX); - deltaY = Math.floor(deltaY); + var vx2 = vp.x + vp.w - 1; + var vy2 = vp.y + vp.h - 1; - if (!this._viewport) { - deltaX = -vp.w; // clamped later of out of bounds - deltaY = -vp.h; - } + // Position change - var vx2 = vp.x + vp.w - 1; - var vy2 = vp.y + vp.h - 1; + if (deltaX < 0 && vp.x + deltaX < 0) { + deltaX = -vp.x; + } + if (vx2 + deltaX >= this._fb_width) { + deltaX -= vx2 + deltaX - this._fb_width + 1; + } - // Position change + if (vp.y + deltaY < 0) { + deltaY = -vp.y; + } + if (vy2 + deltaY >= this._fb_height) { + deltaY -= (vy2 + deltaY - this._fb_height + 1); + } - if (deltaX < 0 && vp.x + deltaX < 0) { - deltaX = -vp.x; - } - if (vx2 + deltaX >= this._fb_width) { - deltaX -= vx2 + deltaX - this._fb_width + 1; - } + if (deltaX === 0 && deltaY === 0) { + return; + } + Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY); - if (vp.y + deltaY < 0) { - deltaY = -vp.y; - } - if (vy2 + deltaY >= this._fb_height) { - deltaY -= (vy2 + deltaY - this._fb_height + 1); - } + vp.x += deltaX; + vp.y += deltaY; - if (deltaX === 0 && deltaY === 0) { - return; - } - Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY); + this._damage(vp.x, vp.y, vp.w, vp.h); - vp.x += deltaX; - vp.y += deltaY; + this.flip(); + }, + + viewportChangeSize: function(width, height) { + + if (!this._viewport || + typeof(width) === "undefined" || + typeof(height) === "undefined") { + + Log.Debug("Setting viewport to full display region"); + width = this._fb_width; + height = this._fb_height; + } + + if (width > this._fb_width) { + width = this._fb_width; + } + if (height > this._fb_height) { + height = this._fb_height; + } + + var vp = this._viewportLoc; + if (vp.w !== width || vp.h !== height) { + vp.w = width; + vp.h = height; + + var canvas = this._target; + canvas.width = width; + canvas.height = height; + + // The position might need to be updated if we've grown + this.viewportChangePos(0, 0); this._damage(vp.x, vp.y, vp.w, vp.h); - this.flip(); - }, - viewportChangeSize: function(width, height) { + // Update the visible size of the target canvas + this._rescale(this._scale); + } + }, - if (!this._viewport || - typeof(width) === "undefined" || - typeof(height) === "undefined") { + absX: function (x) { + return x / this._scale + this._viewportLoc.x; + }, - Util.Debug("Setting viewport to full display region"); - width = this._fb_width; - height = this._fb_height; + absY: function (y) { + return y / this._scale + this._viewportLoc.y; + }, + + resize: function (width, height) { + this._prevDrawStyle = ""; + + this._fb_width = width; + this._fb_height = height; + + var canvas = this._backbuffer; + if (canvas.width !== width || canvas.height !== height) { + + // We have to save the canvas data since changing the size will clear it + var saveImg = null; + if (canvas.width > 0 && canvas.height > 0) { + saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height); } - if (width > this._fb_width) { - width = this._fb_width; - } - if (height > this._fb_height) { - height = this._fb_height; - } - - var vp = this._viewportLoc; - if (vp.w !== width || vp.h !== height) { - vp.w = width; - vp.h = height; - - var canvas = this._target; + if (canvas.width !== width) { canvas.width = width; + } + if (canvas.height !== height) { canvas.height = height; - - // The position might need to be updated if we've grown - this.viewportChangePos(0, 0); - - this._damage(vp.x, vp.y, vp.w, vp.h); - this.flip(); - - // Update the visible size of the target canvas - this._rescale(this._scale); - } - }, - - absX: function (x) { - return x / this._scale + this._viewportLoc.x; - }, - - absY: function (y) { - return y / this._scale + this._viewportLoc.y; - }, - - resize: function (width, height) { - this._prevDrawStyle = ""; - - this._fb_width = width; - this._fb_height = height; - - var canvas = this._backbuffer; - if (canvas.width !== width || canvas.height !== height) { - - // We have to save the canvas data since changing the size will clear it - var saveImg = null; - if (canvas.width > 0 && canvas.height > 0) { - saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height); - } - - if (canvas.width !== width) { - canvas.width = width; - } - if (canvas.height !== height) { - canvas.height = height; - } - - if (saveImg) { - this._drawCtx.putImageData(saveImg, 0, 0); - } } - // Readjust the viewport as it may be incorrectly sized - // and positioned - var vp = this._viewportLoc; - this.viewportChangeSize(vp.w, vp.h); - this.viewportChangePos(0, 0); - }, - - // Track what parts of the visible canvas that need updating - _damage: function(x, y, w, h) { - if (x < this._damageBounds.left) { - this._damageBounds.left = x; + if (saveImg) { + this._drawCtx.putImageData(saveImg, 0, 0); } - if (y < this._damageBounds.top) { - this._damageBounds.top = y; - } - if ((x + w) > this._damageBounds.right) { - this._damageBounds.right = x + w; - } - if ((y + h) > this._damageBounds.bottom) { - this._damageBounds.bottom = y + h; - } - }, + } - // Update the visible canvas with the contents of the - // rendering canvas - flip: function(from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - this._renderQ_push({ - 'type': 'flip' - }); - } else { - var x, y, vx, vy, w, h; + // Readjust the viewport as it may be incorrectly sized + // and positioned + var vp = this._viewportLoc; + this.viewportChangeSize(vp.w, vp.h); + this.viewportChangePos(0, 0); + }, - x = this._damageBounds.left; - y = this._damageBounds.top; - w = this._damageBounds.right - x; - h = this._damageBounds.bottom - y; + // Track what parts of the visible canvas that need updating + _damage: function(x, y, w, h) { + if (x < this._damageBounds.left) { + this._damageBounds.left = x; + } + if (y < this._damageBounds.top) { + this._damageBounds.top = y; + } + if ((x + w) > this._damageBounds.right) { + this._damageBounds.right = x + w; + } + if ((y + h) > this._damageBounds.bottom) { + this._damageBounds.bottom = y + h; + } + }, - vx = x - this._viewportLoc.x; - vy = y - this._viewportLoc.y; - - if (vx < 0) { - w += vx; - x -= vx; - vx = 0; - } - if (vy < 0) { - h += vy; - y -= vy; - vy = 0; - } - - if ((vx + w) > this._viewportLoc.w) { - w = this._viewportLoc.w - vx; - } - if ((vy + h) > this._viewportLoc.h) { - h = this._viewportLoc.h - vy; - } - - if ((w > 0) && (h > 0)) { - // FIXME: We may need to disable image smoothing here - // as well (see copyImage()), but we haven't - // noticed any problem yet. - this._targetCtx.drawImage(this._backbuffer, - x, y, w, h, - vx, vy, w, h); - } - - this._damageBounds.left = this._damageBounds.top = 65535; - this._damageBounds.right = this._damageBounds.bottom = 0; - } - }, - - clear: function () { - if (this._logo) { - this.resize(this._logo.width, this._logo.height); - this.imageRect(0, 0, this._logo.type, this._logo.data); - } else { - this.resize(240, 20); - this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height); - } - this.flip(); - }, - - pending: function() { - return this._renderQ.length > 0; - }, - - flush: function() { - if (this._renderQ.length === 0) { - this._onFlush(); - } else { - this._flushing = true; - } - }, - - fillRect: function (x, y, width, height, color, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - this._renderQ_push({ - 'type': 'fill', - 'x': x, - 'y': y, - 'width': width, - 'height': height, - 'color': color - }); - } else { - this._setFillColor(color); - this._drawCtx.fillRect(x, y, width, height); - this._damage(x, y, width, height); - } - }, - - copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - this._renderQ_push({ - 'type': 'copy', - 'old_x': old_x, - 'old_y': old_y, - 'x': new_x, - 'y': new_y, - 'width': w, - 'height': h, - }); - } else { - // Due to this bug among others [1] we need to disable the image-smoothing to - // avoid getting a blur effect when copying data. - // - // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719 - // - // We need to set these every time since all properties are reset - // when the the size is changed - this._drawCtx.mozImageSmoothingEnabled = false; - this._drawCtx.webkitImageSmoothingEnabled = false; - this._drawCtx.msImageSmoothingEnabled = false; - this._drawCtx.imageSmoothingEnabled = false; - - this._drawCtx.drawImage(this._backbuffer, - old_x, old_y, w, h, - new_x, new_y, w, h); - this._damage(new_x, new_y, w, h); - } - }, - - imageRect: function(x, y, mime, arr) { - var img = new Image(); - img.src = "data: " + mime + ";base64," + Base64.encode(arr); + // Update the visible canvas with the contents of the + // rendering canvas + flip: function(from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { this._renderQ_push({ - 'type': 'img', - 'img': img, - 'x': x, - 'y': y + 'type': 'flip' }); - }, + } else { + var x, y, vx, vy, w, h; - // start updating a tile - startTile: function (x, y, width, height, color) { - this._tile_x = x; - this._tile_y = y; - if (width === 16 && height === 16) { - this._tile = this._tile16x16; - } else { - this._tile = this._drawCtx.createImageData(width, height); + x = this._damageBounds.left; + y = this._damageBounds.top; + w = this._damageBounds.right - x; + h = this._damageBounds.bottom - y; + + vx = x - this._viewportLoc.x; + vy = y - this._viewportLoc.y; + + if (vx < 0) { + w += vx; + x -= vx; + vx = 0; + } + if (vy < 0) { + h += vy; + y -= vy; + vy = 0; } - if (this._prefer_js) { - var bgr; - if (this._true_color) { - bgr = color; - } else { - bgr = this._colourMap[color[0]]; - } - var red = bgr[2]; - var green = bgr[1]; - var blue = bgr[0]; - - var data = this._tile.data; - for (var i = 0; i < width * height * 4; i += 4) { - data[i] = red; - data[i + 1] = green; - data[i + 2] = blue; - data[i + 3] = 255; - } - } else { - this.fillRect(x, y, width, height, color, true); + if ((vx + w) > this._viewportLoc.w) { + w = this._viewportLoc.w - vx; } - }, - - // update sub-rectangle of the current tile - subTile: function (x, y, w, h, color) { - if (this._prefer_js) { - var bgr; - if (this._true_color) { - bgr = color; - } else { - bgr = this._colourMap[color[0]]; - } - var red = bgr[2]; - var green = bgr[1]; - var blue = bgr[0]; - var xend = x + w; - var yend = y + h; - - var data = this._tile.data; - var width = this._tile.width; - for (var j = y; j < yend; j++) { - for (var i = x; i < xend; i++) { - var p = (i + (j * width)) * 4; - data[p] = red; - data[p + 1] = green; - data[p + 2] = blue; - data[p + 3] = 255; - } - } - } else { - this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true); - } - }, - - // draw the current tile to the screen - finishTile: function () { - if (this._prefer_js) { - this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); - this._damage(this._tile_x, this._tile_y, - this._tile.width, this._tile.height); - } - // else: No-op -- already done by setSubTile - }, - - blitImage: function (x, y, width, height, arr, offset, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - // NB(directxman12): it's technically more performant here to use preallocated arrays, - // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, - // this probably isn't getting called *nearly* as much - var new_arr = new Uint8Array(width * height * 4); - new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); - this._renderQ_push({ - 'type': 'blit', - 'data': new_arr, - 'x': x, - 'y': y, - 'width': width, - 'height': height, - }); - } else if (this._true_color) { - this._bgrxImageData(x, y, width, height, arr, offset); - } else { - this._cmapImageData(x, y, width, height, arr, offset); - } - }, - - blitRgbImage: function (x, y , width, height, arr, offset, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - // NB(directxman12): it's technically more performant here to use preallocated arrays, - // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, - // this probably isn't getting called *nearly* as much - var new_arr = new Uint8Array(width * height * 3); - new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); - this._renderQ_push({ - 'type': 'blitRgb', - 'data': new_arr, - 'x': x, - 'y': y, - 'width': width, - 'height': height, - }); - } else if (this._true_color) { - this._rgbImageData(x, y, width, height, arr, offset); - } else { - // probably wrong? - this._cmapImageData(x, y, width, height, arr, offset); - } - }, - - blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - // NB(directxman12): it's technically more performant here to use preallocated arrays, - // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, - // this probably isn't getting called *nearly* as much - var new_arr = new Uint8Array(width * height * 4); - new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); - this._renderQ_push({ - 'type': 'blitRgbx', - 'data': new_arr, - 'x': x, - 'y': y, - 'width': width, - 'height': height, - }); - } else { - this._rgbxImageData(x, y, width, height, arr, offset); - } - }, - - drawImage: function (img, x, y) { - this._drawCtx.drawImage(img, x, y); - this._damage(x, y, img.width, img.height); - }, - - changeCursor: function (pixels, mask, hotx, hoty, w, h) { - if (this._cursor_uri === false) { - Util.Warn("changeCursor called but no cursor data URI support"); - return; + if ((vy + h) > this._viewportLoc.h) { + h = this._viewportLoc.h - vy; } - if (this._true_color) { - Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h); - } else { - Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h, this._colourMap); - } - }, - - defaultCursor: function () { - this._target.style.cursor = "default"; - }, - - disableLocalCursor: function () { - this._target.style.cursor = "none"; - }, - - clippingDisplay: function () { - var vp = this._viewportLoc; - return this._fb_width > vp.w || this._fb_height > vp.h; - }, - - // Overridden getters/setters - set_scale: function (scale) { - this._rescale(scale); - }, - - set_viewport: function (viewport) { - this._viewport = viewport; - // May need to readjust the viewport dimensions - var vp = this._viewportLoc; - this.viewportChangeSize(vp.w, vp.h); - this.viewportChangePos(0, 0); - }, - - get_width: function () { - return this._fb_width; - }, - get_height: function () { - return this._fb_height; - }, - - autoscale: function (containerWidth, containerHeight, downscaleOnly) { - var vp = this._viewportLoc; - var targetAspectRatio = containerWidth / containerHeight; - var fbAspectRatio = vp.w / vp.h; - - var scaleRatio; - if (fbAspectRatio >= targetAspectRatio) { - scaleRatio = containerWidth / vp.w; - } else { - scaleRatio = containerHeight / vp.h; + if ((w > 0) && (h > 0)) { + // FIXME: We may need to disable image smoothing here + // as well (see copyImage()), but we haven't + // noticed any problem yet. + this._targetCtx.drawImage(this._backbuffer, + x, y, w, h, + vx, vy, w, h); } - if (scaleRatio > 1.0 && downscaleOnly) { - scaleRatio = 1.0; - } + this._damageBounds.left = this._damageBounds.top = 65535; + this._damageBounds.right = this._damageBounds.bottom = 0; + } + }, - this._rescale(scaleRatio); - }, + clear: function () { + if (this._logo) { + this.resize(this._logo.width, this._logo.height); + this.imageRect(0, 0, this._logo.type, this._logo.data); + } else { + this.resize(240, 20); + this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height); + } + this.flip(); + }, - // Private Methods - _rescale: function (factor) { - this._scale = factor; - var vp = this._viewportLoc; + pending: function() { + return this._renderQ.length > 0; + }, - // NB(directxman12): If you set the width directly, or set the - // style width to a number, the canvas is cleared. - // However, if you set the style width to a string - // ('NNNpx'), the canvas is scaled without clearing. - var width = Math.round(factor * vp.w) + 'px'; - var height = Math.round(factor * vp.h) + 'px'; + flush: function() { + if (this._renderQ.length === 0) { + this._onFlush(); + } else { + this._flushing = true; + } + }, - if ((this._target.style.width !== width) || - (this._target.style.height !== height)) { - this._target.style.width = width; - this._target.style.height = height; - } - }, + fillRect: function (x, y, width, height, color, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this._renderQ_push({ + 'type': 'fill', + 'x': x, + 'y': y, + 'width': width, + 'height': height, + 'color': color + }); + } else { + this._setFillColor(color); + this._drawCtx.fillRect(x, y, width, height); + this._damage(x, y, width, height); + } + }, - _setFillColor: function (color) { + copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this._renderQ_push({ + 'type': 'copy', + 'old_x': old_x, + 'old_y': old_y, + 'x': new_x, + 'y': new_y, + 'width': w, + 'height': h, + }); + } else { + // Due to this bug among others [1] we need to disable the image-smoothing to + // avoid getting a blur effect when copying data. + // + // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719 + // + // We need to set these every time since all properties are reset + // when the the size is changed + this._drawCtx.mozImageSmoothingEnabled = false; + this._drawCtx.webkitImageSmoothingEnabled = false; + this._drawCtx.msImageSmoothingEnabled = false; + this._drawCtx.imageSmoothingEnabled = false; + + this._drawCtx.drawImage(this._backbuffer, + old_x, old_y, w, h, + new_x, new_y, w, h); + this._damage(new_x, new_y, w, h); + } + }, + + imageRect: function(x, y, mime, arr) { + var img = new Image(); + img.src = "data: " + mime + ";base64," + Base64.encode(arr); + this._renderQ_push({ + 'type': 'img', + 'img': img, + 'x': x, + 'y': y + }); + }, + + // start updating a tile + startTile: function (x, y, width, height, color) { + this._tile_x = x; + this._tile_y = y; + if (width === 16 && height === 16) { + this._tile = this._tile16x16; + } else { + this._tile = this._drawCtx.createImageData(width, height); + } + + if (this._prefer_js) { var bgr; if (this._true_color) { bgr = color; } else { - bgr = this._colourMap[color]; + bgr = this._colourMap[color[0]]; } + var red = bgr[2]; + var green = bgr[1]; + var blue = bgr[0]; - var newStyle = 'rgb(' + bgr[2] + ',' + bgr[1] + ',' + bgr[0] + ')'; - if (newStyle !== this._prevDrawStyle) { - this._drawCtx.fillStyle = newStyle; - this._prevDrawStyle = newStyle; + var data = this._tile.data; + for (var i = 0; i < width * height * 4; i += 4) { + data[i] = red; + data[i + 1] = green; + data[i + 2] = blue; + data[i + 3] = 255; } - }, - - _rgbImageData: function (x, y, width, height, arr, offset) { - var img = this._drawCtx.createImageData(width, height); - var data = img.data; - for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) { - data[i] = arr[j]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j + 2]; - data[i + 3] = 255; // Alpha - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - }, - - _bgrxImageData: function (x, y, width, height, arr, offset) { - var img = this._drawCtx.createImageData(width, height); - var data = img.data; - for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) { - data[i] = arr[j + 2]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j]; - data[i + 3] = 255; // Alpha - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - }, - - _rgbxImageData: function (x, y, width, height, arr, offset) { - // NB(directxman12): arr must be an Type Array view - var img; - if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) { - img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height); - } else { - img = this._drawCtx.createImageData(width, height); - img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4)); - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - }, - - _cmapImageData: function (x, y, width, height, arr, offset) { - var img = this._drawCtx.createImageData(width, height); - var data = img.data; - var cmap = this._colourMap; - for (var i = 0, j = offset; i < width * height * 4; i += 4, j++) { - var bgr = cmap[arr[j]]; - data[i] = bgr[2]; - data[i + 1] = bgr[1]; - data[i + 2] = bgr[0]; - data[i + 3] = 255; // Alpha - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - }, - - _renderQ_push: function (action) { - this._renderQ.push(action); - if (this._renderQ.length === 1) { - // If this can be rendered immediately it will be, otherwise - // the scanner will wait for the relevant event - this._scan_renderQ(); - } - }, - - _resume_renderQ: function() { - // "this" is the object that is ready, not the - // display object - this.removeEventListener('load', this._noVNC_display._resume_renderQ); - this._noVNC_display._scan_renderQ(); - }, - - _scan_renderQ: function () { - var ready = true; - while (ready && this._renderQ.length > 0) { - var a = this._renderQ[0]; - switch (a.type) { - case 'flip': - this.flip(true); - break; - case 'copy': - this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true); - break; - case 'fill': - this.fillRect(a.x, a.y, a.width, a.height, a.color, true); - break; - case 'blit': - this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); - break; - case 'blitRgb': - this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true); - break; - case 'blitRgbx': - this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); - break; - case 'img': - if (a.img.complete) { - this.drawImage(a.img, a.x, a.y); - } else { - a.img._noVNC_display = this; - a.img.addEventListener('load', this._resume_renderQ); - // We need to wait for this image to 'load' - // to keep things in-order - ready = false; - } - break; - } - - if (ready) { - this._renderQ.shift(); - } - } - - if (this._renderQ.length === 0 && this._flushing) { - this._flushing = false; - this._onFlush(); - } - }, - }; - - Util.make_properties(Display, [ - ['target', 'wo', 'dom'], // Canvas element for rendering - ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only) - ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data} - ['true_color', 'rw', 'bool'], // Use true-color pixel data - ['colourMap', 'rw', 'arr'], // Colour map array (when not true-color) - ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0 - ['viewport', 'rw', 'bool'], // Use viewport clipping - ['width', 'ro', 'int'], // Display area width - ['height', 'ro', 'int'], // Display area height - - ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only) - - ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods - ['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI - - ['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished - ]); - - // Class Methods - Display.changeCursor = function (target, pixels, mask, hotx, hoty, w0, h0, cmap) { - var w = w0; - var h = h0; - if (h < w) { - h = w; // increase h to make it square } else { - w = h; // increase w to make it square + this.fillRect(x, y, width, height, color, true); + } + }, + + // update sub-rectangle of the current tile + subTile: function (x, y, w, h, color) { + if (this._prefer_js) { + var bgr; + if (this._true_color) { + bgr = color; + } else { + bgr = this._colourMap[color[0]]; + } + var red = bgr[2]; + var green = bgr[1]; + var blue = bgr[0]; + var xend = x + w; + var yend = y + h; + + var data = this._tile.data; + var width = this._tile.width; + for (var j = y; j < yend; j++) { + for (var i = x; i < xend; i++) { + var p = (i + (j * width)) * 4; + data[p] = red; + data[p + 1] = green; + data[p + 2] = blue; + data[p + 3] = 255; + } + } + } else { + this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true); + } + }, + + // draw the current tile to the screen + finishTile: function () { + if (this._prefer_js) { + this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); + this._damage(this._tile_x, this._tile_y, + this._tile.width, this._tile.height); + } + // else: No-op -- already done by setSubTile + }, + + blitImage: function (x, y, width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + // NB(directxman12): it's technically more performant here to use preallocated arrays, + // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, + // this probably isn't getting called *nearly* as much + var new_arr = new Uint8Array(width * height * 4); + new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); + this._renderQ_push({ + 'type': 'blit', + 'data': new_arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else if (this._true_color) { + this._bgrxImageData(x, y, width, height, arr, offset); + } else { + this._cmapImageData(x, y, width, height, arr, offset); + } + }, + + blitRgbImage: function (x, y , width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + // NB(directxman12): it's technically more performant here to use preallocated arrays, + // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, + // this probably isn't getting called *nearly* as much + var new_arr = new Uint8Array(width * height * 3); + new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); + this._renderQ_push({ + 'type': 'blitRgb', + 'data': new_arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else if (this._true_color) { + this._rgbImageData(x, y, width, height, arr, offset); + } else { + // probably wrong? + this._cmapImageData(x, y, width, height, arr, offset); + } + }, + + blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + // NB(directxman12): it's technically more performant here to use preallocated arrays, + // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, + // this probably isn't getting called *nearly* as much + var new_arr = new Uint8Array(width * height * 4); + new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); + this._renderQ_push({ + 'type': 'blitRgbx', + 'data': new_arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else { + this._rgbxImageData(x, y, width, height, arr, offset); + } + }, + + drawImage: function (img, x, y) { + this._drawCtx.drawImage(img, x, y); + this._damage(x, y, img.width, img.height); + }, + + changeCursor: function (pixels, mask, hotx, hoty, w, h) { + if (this._cursor_uri === false) { + Log.Warn("changeCursor called but no cursor data URI support"); + return; } - var cur = []; + if (this._true_color) { + Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h); + } else { + Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h, this._colourMap); + } + }, - // Push multi-byte little-endian values - cur.push16le = function (num) { - this.push(num & 0xFF, (num >> 8) & 0xFF); - }; - cur.push32le = function (num) { - this.push(num & 0xFF, - (num >> 8) & 0xFF, - (num >> 16) & 0xFF, - (num >> 24) & 0xFF); - }; + defaultCursor: function () { + this._target.style.cursor = "default"; + }, - var IHDRsz = 40; - var RGBsz = w * h * 4; - var XORsz = Math.ceil((w * h) / 8.0); - var ANDsz = Math.ceil((w * h) / 8.0); + disableLocalCursor: function () { + this._target.style.cursor = "none"; + }, - cur.push16le(0); // 0: Reserved - cur.push16le(2); // 2: .CUR type - cur.push16le(1); // 4: Number of images, 1 for non-animated ico + clippingDisplay: function () { + var vp = this._viewportLoc; + return this._fb_width > vp.w || this._fb_height > vp.h; + }, - // Cursor #1 header (ICONDIRENTRY) - cur.push(w); // 6: width - cur.push(h); // 7: height - cur.push(0); // 8: colors, 0 -> true-color - cur.push(0); // 9: reserved - cur.push16le(hotx); // 10: hotspot x coordinate - cur.push16le(hoty); // 12: hotspot y coordinate - cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz); - // 14: cursor data byte size - cur.push32le(22); // 18: offset of cursor data in the file + // Overridden getters/setters + set_scale: function (scale) { + this._rescale(scale); + }, - // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO) - cur.push32le(IHDRsz); // 22: InfoHeader size - cur.push32le(w); // 26: Cursor width - cur.push32le(h * 2); // 30: XOR+AND height - cur.push16le(1); // 34: number of planes - cur.push16le(32); // 36: bits per pixel - cur.push32le(0); // 38: Type of compression + set_viewport: function (viewport) { + this._viewport = viewport; + // May need to readjust the viewport dimensions + var vp = this._viewportLoc; + this.viewportChangeSize(vp.w, vp.h); + this.viewportChangePos(0, 0); + }, - cur.push32le(XORsz + ANDsz); - // 42: Size of Image - cur.push32le(0); // 46: reserved - cur.push32le(0); // 50: reserved - cur.push32le(0); // 54: reserved - cur.push32le(0); // 58: reserved + get_width: function () { + return this._fb_width; + }, + get_height: function () { + return this._fb_height; + }, - // 62: color data (RGBQUAD icColors[]) - var y, x; - for (y = h - 1; y >= 0; y--) { - for (x = 0; x < w; x++) { - if (x >= w0 || y >= h0) { - cur.push(0); // blue - cur.push(0); // green - cur.push(0); // red - cur.push(0); // alpha - } else { - var idx = y * Math.ceil(w0 / 8) + Math.floor(x / 8); - var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; - if (cmap) { - idx = (w0 * y) + x; - var rgb = cmap[pixels[idx]]; - cur.push(rgb[2]); // blue - cur.push(rgb[1]); // green - cur.push(rgb[0]); // red - cur.push(alpha); // alpha + autoscale: function (containerWidth, containerHeight, downscaleOnly) { + var vp = this._viewportLoc; + var targetAspectRatio = containerWidth / containerHeight; + var fbAspectRatio = vp.w / vp.h; + + var scaleRatio; + if (fbAspectRatio >= targetAspectRatio) { + scaleRatio = containerWidth / vp.w; + } else { + scaleRatio = containerHeight / vp.h; + } + + if (scaleRatio > 1.0 && downscaleOnly) { + scaleRatio = 1.0; + } + + this._rescale(scaleRatio); + }, + + // Private Methods + _rescale: function (factor) { + this._scale = factor; + var vp = this._viewportLoc; + + // NB(directxman12): If you set the width directly, or set the + // style width to a number, the canvas is cleared. + // However, if you set the style width to a string + // ('NNNpx'), the canvas is scaled without clearing. + var width = Math.round(factor * vp.w) + 'px'; + var height = Math.round(factor * vp.h) + 'px'; + + if ((this._target.style.width !== width) || + (this._target.style.height !== height)) { + this._target.style.width = width; + this._target.style.height = height; + } + }, + + _setFillColor: function (color) { + var bgr; + if (this._true_color) { + bgr = color; + } else { + bgr = this._colourMap[color]; + } + + var newStyle = 'rgb(' + bgr[2] + ',' + bgr[1] + ',' + bgr[0] + ')'; + if (newStyle !== this._prevDrawStyle) { + this._drawCtx.fillStyle = newStyle; + this._prevDrawStyle = newStyle; + } + }, + + _rgbImageData: function (x, y, width, height, arr, offset) { + var img = this._drawCtx.createImageData(width, height); + var data = img.data; + for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) { + data[i] = arr[j]; + data[i + 1] = arr[j + 1]; + data[i + 2] = arr[j + 2]; + data[i + 3] = 255; // Alpha + } + this._drawCtx.putImageData(img, x, y); + this._damage(x, y, img.width, img.height); + }, + + _bgrxImageData: function (x, y, width, height, arr, offset) { + var img = this._drawCtx.createImageData(width, height); + var data = img.data; + for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) { + data[i] = arr[j + 2]; + data[i + 1] = arr[j + 1]; + data[i + 2] = arr[j]; + data[i + 3] = 255; // Alpha + } + this._drawCtx.putImageData(img, x, y); + this._damage(x, y, img.width, img.height); + }, + + _rgbxImageData: function (x, y, width, height, arr, offset) { + // NB(directxman12): arr must be an Type Array view + var img; + if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) { + img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height); + } else { + img = this._drawCtx.createImageData(width, height); + img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4)); + } + this._drawCtx.putImageData(img, x, y); + this._damage(x, y, img.width, img.height); + }, + + _cmapImageData: function (x, y, width, height, arr, offset) { + var img = this._drawCtx.createImageData(width, height); + var data = img.data; + var cmap = this._colourMap; + for (var i = 0, j = offset; i < width * height * 4; i += 4, j++) { + var bgr = cmap[arr[j]]; + data[i] = bgr[2]; + data[i + 1] = bgr[1]; + data[i + 2] = bgr[0]; + data[i + 3] = 255; // Alpha + } + this._drawCtx.putImageData(img, x, y); + this._damage(x, y, img.width, img.height); + }, + + _renderQ_push: function (action) { + this._renderQ.push(action); + if (this._renderQ.length === 1) { + // If this can be rendered immediately it will be, otherwise + // the scanner will wait for the relevant event + this._scan_renderQ(); + } + }, + + _resume_renderQ: function() { + // "this" is the object that is ready, not the + // display object + this.removeEventListener('load', this._noVNC_display._resume_renderQ); + this._noVNC_display._scan_renderQ(); + }, + + _scan_renderQ: function () { + var ready = true; + while (ready && this._renderQ.length > 0) { + var a = this._renderQ[0]; + switch (a.type) { + case 'flip': + this.flip(true); + break; + case 'copy': + this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true); + break; + case 'fill': + this.fillRect(a.x, a.y, a.width, a.height, a.color, true); + break; + case 'blit': + this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); + break; + case 'blitRgb': + this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true); + break; + case 'blitRgbx': + this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); + break; + case 'img': + if (a.img.complete) { + this.drawImage(a.img, a.x, a.y); } else { - idx = ((w0 * y) + x) * 4; - cur.push(pixels[idx]); // blue - cur.push(pixels[idx + 1]); // green - cur.push(pixels[idx + 2]); // red - cur.push(alpha); // alpha + a.img._noVNC_display = this; + a.img.addEventListener('load', this._resume_renderQ); + // We need to wait for this image to 'load' + // to keep things in-order + ready = false; } + break; + } + + if (ready) { + this._renderQ.shift(); + } + } + + if (this._renderQ.length === 0 && this._flushing) { + this._flushing = false; + this._onFlush(); + } + }, +}; + +make_properties(Display, [ + ['target', 'wo', 'dom'], // Canvas element for rendering + ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only) + ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data} + ['true_color', 'rw', 'bool'], // Use true-color pixel data + ['colourMap', 'rw', 'arr'], // Colour map array (when not true-color) + ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0 + ['viewport', 'rw', 'bool'], // Use viewport clipping + ['width', 'ro', 'int'], // Display area width + ['height', 'ro', 'int'], // Display area height + + ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only) + + ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods + ['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI + + ['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished +]); + +// Class Methods +Display.changeCursor = function (target, pixels, mask, hotx, hoty, w0, h0, cmap) { + var w = w0; + var h = h0; + if (h < w) { + h = w; // increase h to make it square + } else { + w = h; // increase w to make it square + } + + var cur = []; + + // Push multi-byte little-endian values + cur.push16le = function (num) { + this.push(num & 0xFF, (num >> 8) & 0xFF); + }; + cur.push32le = function (num) { + this.push(num & 0xFF, + (num >> 8) & 0xFF, + (num >> 16) & 0xFF, + (num >> 24) & 0xFF); + }; + + var IHDRsz = 40; + var RGBsz = w * h * 4; + var XORsz = Math.ceil((w * h) / 8.0); + var ANDsz = Math.ceil((w * h) / 8.0); + + cur.push16le(0); // 0: Reserved + cur.push16le(2); // 2: .CUR type + cur.push16le(1); // 4: Number of images, 1 for non-animated ico + + // Cursor #1 header (ICONDIRENTRY) + cur.push(w); // 6: width + cur.push(h); // 7: height + cur.push(0); // 8: colors, 0 -> true-color + cur.push(0); // 9: reserved + cur.push16le(hotx); // 10: hotspot x coordinate + cur.push16le(hoty); // 12: hotspot y coordinate + cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz); + // 14: cursor data byte size + cur.push32le(22); // 18: offset of cursor data in the file + + // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO) + cur.push32le(IHDRsz); // 22: InfoHeader size + cur.push32le(w); // 26: Cursor width + cur.push32le(h * 2); // 30: XOR+AND height + cur.push16le(1); // 34: number of planes + cur.push16le(32); // 36: bits per pixel + cur.push32le(0); // 38: Type of compression + + cur.push32le(XORsz + ANDsz); + // 42: Size of Image + cur.push32le(0); // 46: reserved + cur.push32le(0); // 50: reserved + cur.push32le(0); // 54: reserved + cur.push32le(0); // 58: reserved + + // 62: color data (RGBQUAD icColors[]) + var y, x; + for (y = h - 1; y >= 0; y--) { + for (x = 0; x < w; x++) { + if (x >= w0 || y >= h0) { + cur.push(0); // blue + cur.push(0); // green + cur.push(0); // red + cur.push(0); // alpha + } else { + var idx = y * Math.ceil(w0 / 8) + Math.floor(x / 8); + var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; + if (cmap) { + idx = (w0 * y) + x; + var rgb = cmap[pixels[idx]]; + cur.push(rgb[2]); // blue + cur.push(rgb[1]); // green + cur.push(rgb[0]); // red + cur.push(alpha); // alpha + } else { + idx = ((w0 * y) + x) * 4; + cur.push(pixels[idx]); // blue + cur.push(pixels[idx + 1]); // green + cur.push(pixels[idx + 2]); // red + cur.push(alpha); // alpha } } } + } - // XOR/bitmask data (BYTE icXOR[]) - // (ignored, just needs to be the right size) - for (y = 0; y < h; y++) { - for (x = 0; x < Math.ceil(w / 8); x++) { - cur.push(0); - } + // XOR/bitmask data (BYTE icXOR[]) + // (ignored, just needs to be the right size) + for (y = 0; y < h; y++) { + for (x = 0; x < Math.ceil(w / 8); x++) { + cur.push(0); } + } - // AND/bitmask data (BYTE icAND[]) - // (ignored, just needs to be the right size) - for (y = 0; y < h; y++) { - for (x = 0; x < Math.ceil(w / 8); x++) { - cur.push(0); - } + // AND/bitmask data (BYTE icAND[]) + // (ignored, just needs to be the right size) + for (y = 0; y < h; y++) { + for (x = 0; x < Math.ceil(w / 8); x++) { + cur.push(0); } + } - var url = 'data:image/x-icon;base64,' + Base64.encode(cur); - target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default'; - }; -})(); + var url = 'data:image/x-icon;base64,' + Base64.encode(cur); + target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default'; +}; diff --git a/core/inflator.js b/core/inflator.js index 68fd8298..a4d6ff6a 100644 --- a/core/inflator.js +++ b/core/inflator.js @@ -36,4 +36,3 @@ export default function Inflate() { inflateInit(this.strm, this.windowBits); }; - diff --git a/core/input/devices.js b/core/input/devices.js index 74317ca8..22653e38 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -8,395 +8,387 @@ /*jslint browser: true, white: false */ /*global window, Util */ -import Util from "../util.js"; -import KeyboardUtil from "./util.js"; +import * as Log from '../util/logging.js'; +import { isTouchDevice } from '../util/browsers.js' +import { setCapture, releaseCapture, stopEvent, getPointerEvent } from '../util/events.js'; +import { set_defaults, make_properties } from '../util/properties.js'; +import * as KeyboardUtil from "./util.js"; +// +// Keyboard event handler +// -export var Keyboard; +const Keyboard = function (defaults) { + this._keyDownList = []; // List of depressed keys + // (even if they are happy) -(function () { - "use strict"; + set_defaults(this, defaults, { + 'target': document, + 'focused': true + }); - // - // Keyboard event handler - // - - Keyboard = function (defaults) { - this._keyDownList = []; // List of depressed keys - // (even if they are happy) - - Util.set_defaults(this, defaults, { - 'target': document, - 'focused': true - }); - - // create the keyboard handler - this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), - KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */ - KeyboardUtil.TrackKeyState( - KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this)) - ) + // create the keyboard handler + this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), + KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */ + KeyboardUtil.TrackKeyState( + KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this)) ) - ); /* jshint newcap: true */ + ) + ); /* jshint newcap: true */ - // keep these here so we can refer to them later - this._eventHandlers = { - 'keyup': this._handleKeyUp.bind(this), - 'keydown': this._handleKeyDown.bind(this), - 'keypress': this._handleKeyPress.bind(this), - 'blur': this._allKeysUp.bind(this) - }; + // keep these here so we can refer to them later + this._eventHandlers = { + 'keyup': this._handleKeyUp.bind(this), + 'keydown': this._handleKeyDown.bind(this), + 'keypress': this._handleKeyPress.bind(this), + 'blur': this._allKeysUp.bind(this) }; +}; - Keyboard.prototype = { - // private methods +Keyboard.prototype = { + // private methods - _handleRfbEvent: function (e) { - if (this._onKeyPress) { - Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") + - ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")"); - this._onKeyPress(e); - } - }, - - setQEMUVNCKeyboardHandler: function () { - this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(), - KeyboardUtil.TrackQEMUKeyState( - this._handleRfbEvent.bind(this) - ) - ); - }, - - _handleKeyDown: function (e) { - if (!this._focused) { return; } - - if (this._handler.keydown(e)) { - // Suppress bubbling/default actions - Util.stopEvent(e); - } else { - // Allow the event to bubble and become a keyPress event which - // will have the character code translated - } - }, - - _handleKeyPress: function (e) { - if (!this._focused) { return; } - - if (this._handler.keypress(e)) { - // Suppress bubbling/default actions - Util.stopEvent(e); - } - }, - - _handleKeyUp: function (e) { - if (!this._focused) { return; } - - if (this._handler.keyup(e)) { - // Suppress bubbling/default actions - Util.stopEvent(e); - } - }, - - _allKeysUp: function () { - Util.Debug(">> Keyboard.allKeysUp"); - this._handler.releaseAll(); - Util.Debug("<< Keyboard.allKeysUp"); - }, - - // Public methods - - grab: function () { - //Util.Debug(">> Keyboard.grab"); - var c = this._target; - - c.addEventListener('keydown', this._eventHandlers.keydown); - c.addEventListener('keyup', this._eventHandlers.keyup); - c.addEventListener('keypress', this._eventHandlers.keypress); - - // Release (key up) if window loses focus - window.addEventListener('blur', this._eventHandlers.blur); - - //Util.Debug("<< Keyboard.grab"); - }, - - ungrab: function () { - //Util.Debug(">> Keyboard.ungrab"); - var c = this._target; - - c.removeEventListener('keydown', this._eventHandlers.keydown); - c.removeEventListener('keyup', this._eventHandlers.keyup); - c.removeEventListener('keypress', this._eventHandlers.keypress); - window.removeEventListener('blur', this._eventHandlers.blur); - - // Release (key up) all keys that are in a down state - this._allKeysUp(); - - //Util.Debug(">> Keyboard.ungrab"); - }, - - sync: function (e) { - this._handler.syncModifiers(e); + _handleRfbEvent: function (e) { + if (this._onKeyPress) { + Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") + + ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")"); + this._onKeyPress(e); } + }, + + setQEMUVNCKeyboardHandler: function () { + this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(), + KeyboardUtil.TrackQEMUKeyState( + this._handleRfbEvent.bind(this) + ) + ); + }, + + _handleKeyDown: function (e) { + if (!this._focused) { return; } + + if (this._handler.keydown(e)) { + // Suppress bubbling/default actions + stopEvent(e); + } else { + // Allow the event to bubble and become a keyPress event which + // will have the character code translated + } + }, + + _handleKeyPress: function (e) { + if (!this._focused) { return; } + + if (this._handler.keypress(e)) { + // Suppress bubbling/default actions + stopEvent(e); + } + }, + + _handleKeyUp: function (e) { + if (!this._focused) { return; } + + if (this._handler.keyup(e)) { + // Suppress bubbling/default actions + stopEvent(e); + } + }, + + _allKeysUp: function () { + Log.Debug(">> Keyboard.allKeysUp"); + this._handler.releaseAll(); + Log.Debug("<< Keyboard.allKeysUp"); + }, + + // Public methods + + grab: function () { + //Log.Debug(">> Keyboard.grab"); + var c = this._target; + + c.addEventListener('keydown', this._eventHandlers.keydown); + c.addEventListener('keyup', this._eventHandlers.keyup); + c.addEventListener('keypress', this._eventHandlers.keypress); + + // Release (key up) if window loses focus + window.addEventListener('blur', this._eventHandlers.blur); + + //Log.Debug("<< Keyboard.grab"); + }, + + ungrab: function () { + //Log.Debug(">> Keyboard.ungrab"); + var c = this._target; + + c.removeEventListener('keydown', this._eventHandlers.keydown); + c.removeEventListener('keyup', this._eventHandlers.keyup); + c.removeEventListener('keypress', this._eventHandlers.keypress); + window.removeEventListener('blur', this._eventHandlers.blur); + + // Release (key up) all keys that are in a down state + this._allKeysUp(); + + //Log.Debug(">> Keyboard.ungrab"); + }, + + sync: function (e) { + this._handler.syncModifiers(e); + } +}; + +make_properties(Keyboard, [ + ['target', 'wo', 'dom'], // DOM element that captures keyboard input + ['focused', 'rw', 'bool'], // Capture and send key events + + ['onKeyPress', 'rw', 'func'] // Handler for key press/release +]); + +const Mouse = function (defaults) { + this._mouseCaptured = false; + + this._doubleClickTimer = null; + this._lastTouchPos = null; + + // Configuration attributes + set_defaults(this, defaults, { + 'target': document, + 'focused': true, + 'touchButton': 1 + }); + + this._eventHandlers = { + 'mousedown': this._handleMouseDown.bind(this), + 'mouseup': this._handleMouseUp.bind(this), + 'mousemove': this._handleMouseMove.bind(this), + 'mousewheel': this._handleMouseWheel.bind(this), + 'mousedisable': this._handleMouseDisable.bind(this) }; +}; - Util.make_properties(Keyboard, [ - ['target', 'wo', 'dom'], // DOM element that captures keyboard input - ['focused', 'rw', 'bool'], // Capture and send key events +Mouse.prototype = { + // private methods + _captureMouse: function () { + // capturing the mouse ensures we get the mouseup event + setCapture(this._target); - ['onKeyPress', 'rw', 'func'] // Handler for key press/release - ]); -})(); + // some browsers give us mouseup events regardless, + // so if we never captured the mouse, we can disregard the event + this._mouseCaptured = true; + }, -export var Mouse; - -(function () { - Mouse = function (defaults) { - this._mouseCaptured = false; + _releaseMouse: function () { + releaseCapture(); + this._mouseCaptured = false; + }, + _resetDoubleClickTimer: function () { this._doubleClickTimer = null; - this._lastTouchPos = null; + }, - // Configuration attributes - Util.set_defaults(this, defaults, { - 'target': document, - 'focused': true, - 'touchButton': 1 - }); + _handleMouseButton: function (e, down) { + if (!this._focused) { return; } - this._eventHandlers = { - 'mousedown': this._handleMouseDown.bind(this), - 'mouseup': this._handleMouseUp.bind(this), - 'mousemove': this._handleMouseMove.bind(this), - 'mousewheel': this._handleMouseWheel.bind(this), - 'mousedisable': this._handleMouseDisable.bind(this) - }; - }; - - Mouse.prototype = { - // private methods - _captureMouse: function () { - // capturing the mouse ensures we get the mouseup event - Util.setCapture(this._target); - - // some browsers give us mouseup events regardless, - // so if we never captured the mouse, we can disregard the event - this._mouseCaptured = true; - }, - - _releaseMouse: function () { - Util.releaseCapture(); - this._mouseCaptured = false; - }, - - _resetDoubleClickTimer: function () { - this._doubleClickTimer = null; - }, - - _handleMouseButton: function (e, down) { - if (!this._focused) { return; } - - if (this._notify) { - this._notify(e); - } - - var pos = this._getMousePosition(e); - - var bmask; - if (e.touches || e.changedTouches) { - // Touch device - - // When two touches occur within 500 ms of each other and are - // close enough together a double click is triggered. - if (down == 1) { - if (this._doubleClickTimer === null) { - this._lastTouchPos = pos; - } else { - clearTimeout(this._doubleClickTimer); - - // When the distance between the two touches is small enough - // force the position of the latter touch to the position of - // the first. - - var xs = this._lastTouchPos.x - pos.x; - var ys = this._lastTouchPos.y - pos.y; - var d = Math.sqrt((xs * xs) + (ys * ys)); - - // The goal is to trigger on a certain physical width, the - // devicePixelRatio brings us a bit closer but is not optimal. - var threshold = 20 * (window.devicePixelRatio || 1); - if (d < threshold) { - pos = this._lastTouchPos; - } - } - this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); - } - bmask = this._touchButton; - // If bmask is set - } else if (e.which) { - /* everything except IE */ - bmask = 1 << e.button; - } else { - /* IE including 9 */ - bmask = (e.button & 0x1) + // Left - (e.button & 0x2) * 2 + // Right - (e.button & 0x4) / 2; // Middle - } - - if (this._onMouseButton) { - Util.Debug("onMouseButton " + (down ? "down" : "up") + - ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); - this._onMouseButton(pos.x, pos.y, down, bmask); - } - Util.stopEvent(e); - }, - - _handleMouseDown: function (e) { - this._captureMouse(); - this._handleMouseButton(e, 1); - }, - - _handleMouseUp: function (e) { - if (!this._mouseCaptured) { return; } - - this._handleMouseButton(e, 0); - this._releaseMouse(); - }, - - _handleMouseWheel: function (e) { - if (!this._focused) { return; } - - if (this._notify) { - this._notify(e); - } - - var pos = this._getMousePosition(e); - - if (this._onMouseButton) { - if (e.deltaX < 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 5); - this._onMouseButton(pos.x, pos.y, 0, 1 << 5); - } else if (e.deltaX > 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 6); - this._onMouseButton(pos.x, pos.y, 0, 1 << 6); - } - - if (e.deltaY < 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 3); - this._onMouseButton(pos.x, pos.y, 0, 1 << 3); - } else if (e.deltaY > 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 4); - this._onMouseButton(pos.x, pos.y, 0, 1 << 4); - } - } - - Util.stopEvent(e); - }, - - _handleMouseMove: function (e) { - if (! this._focused) { return; } - - if (this._notify) { - this._notify(e); - } - - var pos = this._getMousePosition(e); - if (this._onMouseMove) { - this._onMouseMove(pos.x, pos.y); - } - Util.stopEvent(e); - }, - - _handleMouseDisable: function (e) { - if (!this._focused) { return; } - - /* - * Stop propagation if inside canvas area - * Note: This is only needed for the 'click' event as it fails - * to fire properly for the target element so we have - * to listen on the document element instead. - */ - if (e.target == this._target) { - //Util.Debug("mouse event disabled"); - Util.stopEvent(e); - } - }, - - // Return coordinates relative to target - _getMousePosition: function(e) { - e = Util.getPointerEvent(e); - var bounds = this._target.getBoundingClientRect(); - var x, y; - // Clip to target bounds - if (e.clientX < bounds.left) { - x = 0; - } else if (e.clientX >= bounds.right) { - x = bounds.width - 1; - } else { - x = e.clientX - bounds.left; - } - if (e.clientY < bounds.top) { - y = 0; - } else if (e.clientY >= bounds.bottom) { - y = bounds.height - 1; - } else { - y = e.clientY - bounds.top; - } - return {x:x, y:y}; - }, - - - // Public methods - grab: function () { - var c = this._target; - - if (Util.isTouchDevice) { - c.addEventListener('touchstart', this._eventHandlers.mousedown); - window.addEventListener('touchend', this._eventHandlers.mouseup); - c.addEventListener('touchend', this._eventHandlers.mouseup); - c.addEventListener('touchmove', this._eventHandlers.mousemove); - } - c.addEventListener('mousedown', this._eventHandlers.mousedown); - window.addEventListener('mouseup', this._eventHandlers.mouseup); - c.addEventListener('mouseup', this._eventHandlers.mouseup); - c.addEventListener('mousemove', this._eventHandlers.mousemove); - c.addEventListener('wheel', this._eventHandlers.mousewheel); - - /* Prevent middle-click pasting (see above for why we bind to document) */ - document.addEventListener('click', this._eventHandlers.mousedisable); - - /* preventDefault() on mousedown doesn't stop this event for some - reason so we have to explicitly block it */ - c.addEventListener('contextmenu', this._eventHandlers.mousedisable); - }, - - ungrab: function () { - var c = this._target; - - if (Util.isTouchDevice) { - c.removeEventListener('touchstart', this._eventHandlers.mousedown); - window.removeEventListener('touchend', this._eventHandlers.mouseup); - c.removeEventListener('touchend', this._eventHandlers.mouseup); - c.removeEventListener('touchmove', this._eventHandlers.mousemove); - } - c.removeEventListener('mousedown', this._eventHandlers.mousedown); - window.removeEventListener('mouseup', this._eventHandlers.mouseup); - c.removeEventListener('mouseup', this._eventHandlers.mouseup); - c.removeEventListener('mousemove', this._eventHandlers.mousemove); - c.removeEventListener('wheel', this._eventHandlers.mousewheel); - - document.removeEventListener('click', this._eventHandlers.mousedisable); - - c.removeEventListener('contextmenu', this._eventHandlers.mousedisable); + if (this._notify) { + this._notify(e); } - }; - Util.make_properties(Mouse, [ - ['target', 'ro', 'dom'], // DOM element that captures mouse input - ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received - ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement + var pos = this._getMousePosition(e); - ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release - ['onMouseMove', 'rw', 'func'], // Handler for mouse movement - ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) - ]); -})(); + var bmask; + if (e.touches || e.changedTouches) { + // Touch device + + // When two touches occur within 500 ms of each other and are + // close enough together a double click is triggered. + if (down == 1) { + if (this._doubleClickTimer === null) { + this._lastTouchPos = pos; + } else { + clearTimeout(this._doubleClickTimer); + + // When the distance between the two touches is small enough + // force the position of the latter touch to the position of + // the first. + + var xs = this._lastTouchPos.x - pos.x; + var ys = this._lastTouchPos.y - pos.y; + var d = Math.sqrt((xs * xs) + (ys * ys)); + + // The goal is to trigger on a certain physical width, the + // devicePixelRatio brings us a bit closer but is not optimal. + var threshold = 20 * (window.devicePixelRatio || 1); + if (d < threshold) { + pos = this._lastTouchPos; + } + } + this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); + } + bmask = this._touchButton; + // If bmask is set + } else if (e.which) { + /* everything except IE */ + bmask = 1 << e.button; + } else { + /* IE including 9 */ + bmask = (e.button & 0x1) + // Left + (e.button & 0x2) * 2 + // Right + (e.button & 0x4) / 2; // Middle + } + + if (this._onMouseButton) { + Log.Debug("onMouseButton " + (down ? "down" : "up") + + ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); + this._onMouseButton(pos.x, pos.y, down, bmask); + } + stopEvent(e); + }, + + _handleMouseDown: function (e) { + this._captureMouse(); + this._handleMouseButton(e, 1); + }, + + _handleMouseUp: function (e) { + if (!this._mouseCaptured) { return; } + + this._handleMouseButton(e, 0); + this._releaseMouse(); + }, + + _handleMouseWheel: function (e) { + if (!this._focused) { return; } + + if (this._notify) { + this._notify(e); + } + + var pos = this._getMousePosition(e); + + if (this._onMouseButton) { + if (e.deltaX < 0) { + this._onMouseButton(pos.x, pos.y, 1, 1 << 5); + this._onMouseButton(pos.x, pos.y, 0, 1 << 5); + } else if (e.deltaX > 0) { + this._onMouseButton(pos.x, pos.y, 1, 1 << 6); + this._onMouseButton(pos.x, pos.y, 0, 1 << 6); + } + + if (e.deltaY < 0) { + this._onMouseButton(pos.x, pos.y, 1, 1 << 3); + this._onMouseButton(pos.x, pos.y, 0, 1 << 3); + } else if (e.deltaY > 0) { + this._onMouseButton(pos.x, pos.y, 1, 1 << 4); + this._onMouseButton(pos.x, pos.y, 0, 1 << 4); + } + } + + stopEvent(e); + }, + + _handleMouseMove: function (e) { + if (! this._focused) { return; } + + if (this._notify) { + this._notify(e); + } + + var pos = this._getMousePosition(e); + if (this._onMouseMove) { + this._onMouseMove(pos.x, pos.y); + } + stopEvent(e); + }, + + _handleMouseDisable: function (e) { + if (!this._focused) { return; } + + /* + * Stop propagation if inside canvas area + * Note: This is only needed for the 'click' event as it fails + * to fire properly for the target element so we have + * to listen on the document element instead. + */ + if (e.target == this._target) { + stopEvent(e); + } + }, + + // Return coordinates relative to target + _getMousePosition: function(e) { + e = getPointerEvent(e); + var bounds = this._target.getBoundingClientRect(); + var x, y; + // Clip to target bounds + if (e.clientX < bounds.left) { + x = 0; + } else if (e.clientX >= bounds.right) { + x = bounds.width - 1; + } else { + x = e.clientX - bounds.left; + } + if (e.clientY < bounds.top) { + y = 0; + } else if (e.clientY >= bounds.bottom) { + y = bounds.height - 1; + } else { + y = e.clientY - bounds.top; + } + return {x:x, y:y}; + }, + + // Public methods + grab: function () { + var c = this._target; + + if (isTouchDevice) { + c.addEventListener('touchstart', this._eventHandlers.mousedown); + window.addEventListener('touchend', this._eventHandlers.mouseup); + c.addEventListener('touchend', this._eventHandlers.mouseup); + c.addEventListener('touchmove', this._eventHandlers.mousemove); + } + c.addEventListener('mousedown', this._eventHandlers.mousedown); + window.addEventListener('mouseup', this._eventHandlers.mouseup); + c.addEventListener('mouseup', this._eventHandlers.mouseup); + c.addEventListener('mousemove', this._eventHandlers.mousemove); + c.addEventListener('wheel', this._eventHandlers.mousewheel); + + /* Prevent middle-click pasting (see above for why we bind to document) */ + document.addEventListener('click', this._eventHandlers.mousedisable); + + /* preventDefault() on mousedown doesn't stop this event for some + reason so we have to explicitly block it */ + c.addEventListener('contextmenu', this._eventHandlers.mousedisable); + }, + + ungrab: function () { + var c = this._target; + + if (isTouchDevice) { + c.removeEventListener('touchstart', this._eventHandlers.mousedown); + window.removeEventListener('touchend', this._eventHandlers.mouseup); + c.removeEventListener('touchend', this._eventHandlers.mouseup); + c.removeEventListener('touchmove', this._eventHandlers.mousemove); + } + c.removeEventListener('mousedown', this._eventHandlers.mousedown); + window.removeEventListener('mouseup', this._eventHandlers.mouseup); + c.removeEventListener('mouseup', this._eventHandlers.mouseup); + c.removeEventListener('mousemove', this._eventHandlers.mousemove); + c.removeEventListener('wheel', this._eventHandlers.mousewheel); + + document.removeEventListener('click', this._eventHandlers.mousedisable); + + c.removeEventListener('contextmenu', this._eventHandlers.mousedisable); + } +}; + +make_properties(Mouse, [ + ['target', 'ro', 'dom'], // DOM element that captures mouse input + ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received + ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement + + ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release + ['onMouseMove', 'rw', 'func'], // Handler for mouse movement + ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) +]); + +export { Keyboard, Mouse }; diff --git a/core/input/keysym.js b/core/input/keysym.js index 15cc1bc7..f3a247fd 100644 --- a/core/input/keysym.js +++ b/core/input/keysym.js @@ -1,4 +1,4 @@ -var KeyTable = { +export default { XK_VoidSymbol: 0xffffff, /* Void symbol */ XK_BackSpace: 0xff08, /* Back space, back char */ @@ -378,5 +378,3 @@ var KeyTable = { XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ }; - -export default KeyTable; diff --git a/core/input/keysymdef.js b/core/input/keysymdef.js index e618c167..3d9cee39 100644 --- a/core/input/keysymdef.js +++ b/core/input/keysymdef.js @@ -3,22 +3,17 @@ // How this file was generated: // node /Users/jalf/dev/mi/novnc/utils/parse.js /opt/X11/include/X11/keysymdef.h -var keysyms = (function(){ - "use strict"; - var keynames = null; - var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200}; +var keynames = null; +var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200}; - function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; } - return { - fromUnicode : function(u) { - var keysym = codepoints[u]; - if (keysym === undefined) { - keysym = 0x01000000 | u; - } - return lookup(keysym); - }, - lookup : lookup - }; -})(); - -export default keysyms +function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; } +export default { + fromUnicode : function(u) { + var keysym = codepoints[u]; + if (keysym === undefined) { + keysym = 0x01000000 | u; + } + return lookup(keysym); + }, + lookup : lookup +}; diff --git a/core/input/util.js b/core/input/util.js index f34a9abf..b1e63711 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -1,293 +1,277 @@ import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; +export function substituteCodepoint(cp) { + // Any Unicode code points which do not have corresponding keysym entries + // can be swapped out for another code point by adding them to this table + var substitutions = { + // {S,s} with comma below -> {S,s} with cedilla + 0x218 : 0x15e, + 0x219 : 0x15f, + // {T,t} with comma below -> {T,t} with cedilla + 0x21a : 0x162, + 0x21b : 0x163 + }; -var KeyboardUtil = {}; + var sub = substitutions[cp]; + return sub ? sub : cp; +} -(function() { - "use strict"; +function isMac() { + return navigator && !!(/mac/i).exec(navigator.platform); +} +function isWindows() { + return navigator && !!(/win/i).exec(navigator.platform); +} +function isLinux() { + return navigator && !!(/linux/i).exec(navigator.platform); +} - function substituteCodepoint(cp) { - // Any Unicode code points which do not have corresponding keysym entries - // can be swapped out for another code point by adding them to this table - var substitutions = { - // {S,s} with comma below -> {S,s} with cedilla - 0x218 : 0x15e, - 0x219 : 0x15f, - // {T,t} with comma below -> {T,t} with cedilla - 0x21a : 0x162, - 0x21b : 0x163 - }; - - var sub = substitutions[cp]; - return sub ? sub : cp; - } - - function isMac() { - return navigator && !!(/mac/i).exec(navigator.platform); - } - function isWindows() { - return navigator && !!(/win/i).exec(navigator.platform); - } - function isLinux() { - return navigator && !!(/linux/i).exec(navigator.platform); - } - - // Return true if a modifier which is not the specified char modifier (and is not shift) is down - function hasShortcutModifier(charModifier, currentModifiers) { - var mods = {}; - for (var key in currentModifiers) { - if (parseInt(key) !== KeyTable.XK_Shift_L) { - mods[key] = currentModifiers[key]; - } +// Return true if a modifier which is not the specified char modifier (and is not shift) is down +export function hasShortcutModifier(charModifier, currentModifiers) { + var mods = {}; + for (var key in currentModifiers) { + if (parseInt(key) !== KeyTable.XK_Shift_L) { + mods[key] = currentModifiers[key]; } + } - var sum = 0; - for (var k in currentModifiers) { - if (mods[k]) { - ++sum; - } + var sum = 0; + for (var k in currentModifiers) { + if (mods[k]) { + ++sum; } - if (hasCharModifier(charModifier, mods)) { - return sum > charModifier.length; + } + if (hasCharModifier(charModifier, mods)) { + return sum > charModifier.length; + } + else { + return sum > 0; + } +} + +// Return true if the specified char modifier is currently down +export function hasCharModifier(charModifier, currentModifiers) { + if (charModifier.length === 0) { return false; } + + for (var i = 0; i < charModifier.length; ++i) { + if (!currentModifiers[charModifier[i]]) { + return false; + } + } + return true; +} + +// Helper object tracking modifier key state +// and generates fake key events to compensate if it gets out of sync +export function ModifierSync(charModifier) { + if (!charModifier) { + if (isMac()) { + // on Mac, Option (AKA Alt) is used as a char modifier + charModifier = [KeyTable.XK_Alt_L]; + } + else if (isWindows()) { + // on Windows, Ctrl+Alt is used as a char modifier + charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L]; + } + else if (isLinux()) { + // on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier + charModifier = [KeyTable.XK_ISO_Level3_Shift]; } else { - return sum > 0; + charModifier = []; } } - // Return true if the specified char modifier is currently down - function hasCharModifier(charModifier, currentModifiers) { - if (charModifier.length === 0) { return false; } + var state = {}; + state[KeyTable.XK_Control_L] = false; + state[KeyTable.XK_Alt_L] = false; + state[KeyTable.XK_ISO_Level3_Shift] = false; + state[KeyTable.XK_Shift_L] = false; + state[KeyTable.XK_Meta_L] = false; - for (var i = 0; i < charModifier.length; ++i) { - if (!currentModifiers[charModifier[i]]) { - return false; - } + function sync(evt, keysym) { + var result = []; + function syncKey(keysym) { + return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'}; } - return true; + + if (evt.ctrlKey !== undefined && + evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) { + state[KeyTable.XK_Control_L] = evt.ctrlKey; + result.push(syncKey(KeyTable.XK_Control_L)); + } + if (evt.altKey !== undefined && + evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) { + state[KeyTable.XK_Alt_L] = evt.altKey; + result.push(syncKey(KeyTable.XK_Alt_L)); + } + if (evt.altGraphKey !== undefined && + evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) { + state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey; + result.push(syncKey(KeyTable.XK_ISO_Level3_Shift)); + } + if (evt.shiftKey !== undefined && + evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) { + state[KeyTable.XK_Shift_L] = evt.shiftKey; + result.push(syncKey(KeyTable.XK_Shift_L)); + } + if (evt.metaKey !== undefined && + evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) { + state[KeyTable.XK_Meta_L] = evt.metaKey; + result.push(syncKey(KeyTable.XK_Meta_L)); + } + return result; + } + function syncKeyEvent(evt, down) { + var obj = getKeysym(evt); + var keysym = obj ? obj.keysym : null; + + // first, apply the event itself, if relevant + if (keysym !== null && state[keysym] !== undefined) { + state[keysym] = down; + } + return sync(evt, keysym); } - // Helper object tracking modifier key state - // and generates fake key events to compensate if it gets out of sync - function ModifierSync(charModifier) { - if (!charModifier) { - if (isMac()) { - // on Mac, Option (AKA Alt) is used as a char modifier - charModifier = [KeyTable.XK_Alt_L]; - } - else if (isWindows()) { - // on Windows, Ctrl+Alt is used as a char modifier - charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L]; - } - else if (isLinux()) { - // on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier - charModifier = [KeyTable.XK_ISO_Level3_Shift]; - } - else { - charModifier = []; - } - } + return { + // sync on the appropriate keyboard event + keydown: function(evt) { return syncKeyEvent(evt, true);}, + keyup: function(evt) { return syncKeyEvent(evt, false);}, + // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway + syncAny: function(evt) { return sync(evt);}, - var state = {}; - state[KeyTable.XK_Control_L] = false; - state[KeyTable.XK_Alt_L] = false; - state[KeyTable.XK_ISO_Level3_Shift] = false; - state[KeyTable.XK_Shift_L] = false; - state[KeyTable.XK_Meta_L] = false; + // is a shortcut modifier down? + hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); }, + // if a char modifier is down, return the keys it consists of, otherwise return null + activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; } + }; +} - function sync(evt, keysym) { - var result = []; - function syncKey(keysym) { - return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'}; - } - - if (evt.ctrlKey !== undefined && - evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) { - state[KeyTable.XK_Control_L] = evt.ctrlKey; - result.push(syncKey(KeyTable.XK_Control_L)); - } - if (evt.altKey !== undefined && - evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) { - state[KeyTable.XK_Alt_L] = evt.altKey; - result.push(syncKey(KeyTable.XK_Alt_L)); - } - if (evt.altGraphKey !== undefined && - evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) { - state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey; - result.push(syncKey(KeyTable.XK_ISO_Level3_Shift)); - } - if (evt.shiftKey !== undefined && - evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) { - state[KeyTable.XK_Shift_L] = evt.shiftKey; - result.push(syncKey(KeyTable.XK_Shift_L)); - } - if (evt.metaKey !== undefined && - evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) { - state[KeyTable.XK_Meta_L] = evt.metaKey; - result.push(syncKey(KeyTable.XK_Meta_L)); - } - return result; - } - function syncKeyEvent(evt, down) { - var obj = getKeysym(evt); - var keysym = obj ? obj.keysym : null; - - // first, apply the event itself, if relevant - if (keysym !== null && state[keysym] !== undefined) { - state[keysym] = down; - } - return sync(evt, keysym); - } - - return { - // sync on the appropriate keyboard event - keydown: function(evt) { return syncKeyEvent(evt, true);}, - keyup: function(evt) { return syncKeyEvent(evt, false);}, - // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway - syncAny: function(evt) { return sync(evt);}, - - // is a shortcut modifier down? - hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); }, - // if a char modifier is down, return the keys it consists of, otherwise return null - activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; } - }; +// Get a key ID from a keyboard event +// May be a string or an integer depending on the available properties +export function getKey(evt){ + if ('keyCode' in evt && 'key' in evt) { + return evt.key + ':' + evt.keyCode; } - - // Get a key ID from a keyboard event - // May be a string or an integer depending on the available properties - function getKey(evt){ - if ('keyCode' in evt && 'key' in evt) { - return evt.key + ':' + evt.keyCode; - } - else if ('keyCode' in evt) { - return evt.keyCode; - } - else { - return evt.key; - } + else if ('keyCode' in evt) { + return evt.keyCode; } + else { + return evt.key; + } +} - // Get the most reliable keysym value we can get from a key event - // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which - function getKeysym(evt){ - var codepoint; - if (evt.char && evt.char.length === 1) { - codepoint = evt.char.charCodeAt(); - } - else if (evt.charCode) { - codepoint = evt.charCode; - } - else if (evt.keyCode && evt.type === 'keypress') { - // IE10 stores the char code as keyCode, and has no other useful properties - codepoint = evt.keyCode; - } - if (codepoint) { - return keysyms.fromUnicode(substituteCodepoint(codepoint)); - } - // we could check evt.key here. - // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, - // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key - // so we don't *need* it yet - if (evt.keyCode) { - return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey)); - } - if (evt.which) { - return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey)); - } +// Get the most reliable keysym value we can get from a key event +// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which +export function getKeysym(evt){ + var codepoint; + if (evt.char && evt.char.length === 1) { + codepoint = evt.char.charCodeAt(); + } + else if (evt.charCode) { + codepoint = evt.charCode; + } + else if (evt.keyCode && evt.type === 'keypress') { + // IE10 stores the char code as keyCode, and has no other useful properties + codepoint = evt.keyCode; + } + if (codepoint) { + return keysyms.fromUnicode(substituteCodepoint(codepoint)); + } + // we could check evt.key here. + // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, + // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key + // so we don't *need* it yet + if (evt.keyCode) { + return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey)); + } + if (evt.which) { + return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey)); + } + return null; +} + +// Given a keycode, try to predict which keysym it might be. +// If the keycode is unknown, null is returned. +export function keysymFromKeyCode(keycode, shiftPressed) { + if (typeof(keycode) !== 'number') { return null; } - - // Given a keycode, try to predict which keysym it might be. - // If the keycode is unknown, null is returned. - function keysymFromKeyCode(keycode, shiftPressed) { - if (typeof(keycode) !== 'number') { - return null; - } - // won't be accurate for azerty - if (keycode >= 0x30 && keycode <= 0x39) { - return keycode; // digit - } - if (keycode >= 0x41 && keycode <= 0x5a) { - // remap to lowercase unless shift is down - return shiftPressed ? keycode : keycode + 32; // A-Z - } - if (keycode >= 0x60 && keycode <= 0x69) { - return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9 - } - - switch(keycode) { - case 0x20: return KeyTable.XK_space; - case 0x6a: return KeyTable.XK_KP_Multiply; - case 0x6b: return KeyTable.XK_KP_Add; - case 0x6c: return KeyTable.XK_KP_Separator; - case 0x6d: return KeyTable.XK_KP_Subtract; - case 0x6e: return KeyTable.XK_KP_Decimal; - case 0x6f: return KeyTable.XK_KP_Divide; - case 0xbb: return KeyTable.XK_plus; - case 0xbc: return KeyTable.XK_comma; - case 0xbd: return KeyTable.XK_minus; - case 0xbe: return KeyTable.XK_period; - } - - return nonCharacterKey({keyCode: keycode}); + // won't be accurate for azerty + if (keycode >= 0x30 && keycode <= 0x39) { + return keycode; // digit + } + if (keycode >= 0x41 && keycode <= 0x5a) { + // remap to lowercase unless shift is down + return shiftPressed ? keycode : keycode + 32; // A-Z + } + if (keycode >= 0x60 && keycode <= 0x69) { + return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9 } - // if the key is a known non-character key (any key which doesn't generate character data) - // return its keysym value. Otherwise return null - function nonCharacterKey(evt) { - // evt.key not implemented yet - if (!evt.keyCode) { return null; } - var keycode = evt.keyCode; - - if (keycode >= 0x70 && keycode <= 0x87) { - return KeyTable.XK_F1 + keycode - 0x70; // F1-F24 - } - switch (keycode) { - - case 8 : return KeyTable.XK_BackSpace; - case 13 : return KeyTable.XK_Return; - - case 9 : return KeyTable.XK_Tab; - - case 27 : return KeyTable.XK_Escape; - case 46 : return KeyTable.XK_Delete; - - case 36 : return KeyTable.XK_Home; - case 35 : return KeyTable.XK_End; - case 33 : return KeyTable.XK_Page_Up; - case 34 : return KeyTable.XK_Page_Down; - case 45 : return KeyTable.XK_Insert; - - case 37 : return KeyTable.XK_Left; - case 38 : return KeyTable.XK_Up; - case 39 : return KeyTable.XK_Right; - case 40 : return KeyTable.XK_Down; - - case 16 : return KeyTable.XK_Shift_L; - case 17 : return KeyTable.XK_Control_L; - case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac - - case 224 : return KeyTable.XK_Meta_L; - case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr - case 91 : return KeyTable.XK_Super_L; // also: Windows-key - case 92 : return KeyTable.XK_Super_R; // also: Windows-key - case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac - default: return null; - } + switch(keycode) { + case 0x20: return KeyTable.XK_space; + case 0x6a: return KeyTable.XK_KP_Multiply; + case 0x6b: return KeyTable.XK_KP_Add; + case 0x6c: return KeyTable.XK_KP_Separator; + case 0x6d: return KeyTable.XK_KP_Subtract; + case 0x6e: return KeyTable.XK_KP_Decimal; + case 0x6f: return KeyTable.XK_KP_Divide; + case 0xbb: return KeyTable.XK_plus; + case 0xbc: return KeyTable.XK_comma; + case 0xbd: return KeyTable.XK_minus; + case 0xbe: return KeyTable.XK_period; } - KeyboardUtil.hasShortcutModifier = hasShortcutModifier; - KeyboardUtil.hasCharModifier = hasCharModifier; - KeyboardUtil.ModifierSync = ModifierSync; - KeyboardUtil.getKey = getKey; - KeyboardUtil.getKeysym = getKeysym; - KeyboardUtil.keysymFromKeyCode = keysymFromKeyCode; - KeyboardUtil.nonCharacterKey = nonCharacterKey; - KeyboardUtil.substituteCodepoint = substituteCodepoint; -})(); + return nonCharacterKey({keyCode: keycode}); +} -KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) { +// if the key is a known non-character key (any key which doesn't generate character data) +// return its keysym value. Otherwise return null +export function nonCharacterKey(evt) { + // evt.key not implemented yet + if (!evt.keyCode) { return null; } + var keycode = evt.keyCode; + + if (keycode >= 0x70 && keycode <= 0x87) { + return KeyTable.XK_F1 + keycode - 0x70; // F1-F24 + } + switch (keycode) { + + case 8 : return KeyTable.XK_BackSpace; + case 13 : return KeyTable.XK_Return; + + case 9 : return KeyTable.XK_Tab; + + case 27 : return KeyTable.XK_Escape; + case 46 : return KeyTable.XK_Delete; + + case 36 : return KeyTable.XK_Home; + case 35 : return KeyTable.XK_End; + case 33 : return KeyTable.XK_Page_Up; + case 34 : return KeyTable.XK_Page_Down; + case 45 : return KeyTable.XK_Insert; + + case 37 : return KeyTable.XK_Left; + case 38 : return KeyTable.XK_Up; + case 39 : return KeyTable.XK_Right; + case 40 : return KeyTable.XK_Down; + + case 16 : return KeyTable.XK_Shift_L; + case 17 : return KeyTable.XK_Control_L; + case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac + + case 224 : return KeyTable.XK_Meta_L; + case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr + case 91 : return KeyTable.XK_Super_L; // also: Windows-key + case 92 : return KeyTable.XK_Super_R; // also: Windows-key + case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac + default: return null; + } +} + +export function QEMUKeyEventDecoder (modifierState, next) { "use strict"; function sendAll(evts) { @@ -333,7 +317,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) { var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); var isShift = evt.keyCode === 0x10 || evt.key === 'Shift'; - var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!KeyboardUtil.nonCharacterKey(evt)); + var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); next(result); return suppress; @@ -357,7 +341,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) { }; }; -KeyboardUtil.TrackQEMUKeyState = function(next) { +export function TrackQEMUKeyState (next) { "use strict"; var state = []; @@ -425,7 +409,7 @@ KeyboardUtil.TrackQEMUKeyState = function(next) { // - marks each event with an 'escape' property if a modifier was down which should be "escaped" // - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown // This information is collected into an object which is passed to the next() function. (one call per event) -KeyboardUtil.KeyEventDecoder = function(modifierState, next) { +export function KeyEventDecoder (modifierState, next) { "use strict"; function sendAll(evts) { for (var i = 0; i < evts.length; ++i) { @@ -434,18 +418,18 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) { } function process(evt, type) { var result = {type: type}; - var keyId = KeyboardUtil.getKey(evt); + var keyId = getKey(evt); if (keyId) { result.keyId = keyId; } - var keysym = KeyboardUtil.getKeysym(evt); + var keysym = getKeysym(evt); var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress? // "special" keys like enter, tab or backspace don't send keypress events, // and some browsers don't send keypresses at all if a modifier is down - if (keysym && (type !== 'keydown' || KeyboardUtil.nonCharacterKey(evt) || hasModifier)) { + if (keysym && (type !== 'keydown' || nonCharacterKey(evt) || hasModifier)) { result.keysym = keysym; } @@ -454,11 +438,11 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) { // Should we prevent the browser from handling the event? // Doing so on a keydown (in most browsers) prevents keypress from being generated // so only do that if we have to. - var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!KeyboardUtil.nonCharacterKey(evt)); + var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); // If a char modifier is down on a keydown, we need to insert a stall, // so VerifyCharModifier knows to wait and see if a keypress is comnig - var stall = type === 'keydown' && modifierState.activeCharModifier() && !KeyboardUtil.nonCharacterKey(evt); + var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt); // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) var active = modifierState.activeCharModifier(); @@ -512,7 +496,7 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) { // so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not. // The only way we can distinguish these cases is to wait and see if a keypress event arrives // When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two -KeyboardUtil.VerifyCharModifier = function(next) { +export function VerifyCharModifier (next) { "use strict"; var queue = []; var timer = null; @@ -569,7 +553,7 @@ KeyboardUtil.VerifyCharModifier = function(next) { // in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars // key repeat events should be merged into a single entry. // Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess -KeyboardUtil.TrackKeyState = function(next) { +export function TrackKeyState (next) { "use strict"; var state = []; @@ -653,7 +637,7 @@ KeyboardUtil.TrackKeyState = function(next) { // Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @), // then the modifier must be "undone" before sending the @, and "redone" afterwards. -KeyboardUtil.EscapeModifiers = function(next) { +export function EscapeModifiers (next) { "use strict"; return function(evt) { if (evt.type !== 'keydown' || evt.escape === undefined) { @@ -674,5 +658,3 @@ KeyboardUtil.EscapeModifiers = function(next) { /* jshint shadow: false */ }; }; - -export default KeyboardUtil; diff --git a/core/input/xtscancodes.js b/core/input/xtscancodes.js index 611a80be..43ea51b7 100644 --- a/core/input/xtscancodes.js +++ b/core/input/xtscancodes.js @@ -1,4 +1,4 @@ -var XtScancode = { +export default { "Escape": 0x0001, "Digit1": 0x0002, "Digit2": 0x0003, @@ -147,5 +147,3 @@ var XtScancode = { "LaunchMail": 0xE06C, "MediaSelect": 0xE06D, }; - -export default XtScancode diff --git a/core/rfb.js b/core/rfb.js index 97667b43..55ca2275 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -10,7 +10,10 @@ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) */ -import Util from "./util.js"; +import * as Log from './util/logging.js'; +import _ from './util/localization.js'; +import { decodeUTF8 } from './util/strings.js'; +import { set_defaults, make_properties } from './util/properties.js'; import Display from "./display.js"; import { Keyboard, Mouse } from "./input/devices.js"; import Websock from "./websock.js"; @@ -143,7 +146,7 @@ export default function RFB(defaults) { this._qemuExtKeyEventSupported = false; // set the default value on user-facing properties - Util.set_defaults(this, defaults, { + set_defaults(this, defaults, { 'target': 'null', // VNC display rendering Canvas object 'focusContainer': document, // DOM element that captures keyboard input 'encrypt': false, // Use TLS/SSL/wss encryption @@ -172,7 +175,7 @@ export default function RFB(defaults) { }); // main setup - Util.Debug(">> RFB.constructor"); + Log.Debug(">> RFB.constructor"); // populate encHandlers with bound versions Object.keys(RFB.encodingHandlers).forEach(function (encName) { @@ -192,7 +195,7 @@ export default function RFB(defaults) { this._display = new Display({target: this._target, onFlush: this._onFlush.bind(this)}); } catch (exc) { - Util.Error("Display exception: " + exc); + Log.Error("Display exception: " + exc); throw exc; } @@ -210,13 +213,13 @@ export default function RFB(defaults) { if ((this._rfb_connection_state === 'connecting') && (this._rfb_init_state === '')) { this._rfb_init_state = 'ProtocolVersion'; - Util.Debug("Starting VNC handshake"); + Log.Debug("Starting VNC handshake"); } else { this._fail("Unexpected server connection"); } }.bind(this)); this._sock.on('close', function (e) { - Util.Warn("WebSocket on-close event"); + Log.Warn("WebSocket on-close event"); var msg = ""; if (e.code) { msg = " (code: " + e.code; @@ -249,1868 +252,1853 @@ export default function RFB(defaults) { this._sock.off('close'); }.bind(this)); this._sock.on('error', function (e) { - Util.Warn("WebSocket on-error event"); + Log.Warn("WebSocket on-error event"); }); this._init_vars(); this._cleanup(); var rmode = this._display.get_render_mode(); - Util.Info("Using native WebSockets, render mode: " + rmode); + Log.Info("Using native WebSockets, render mode: " + rmode); - Util.Debug("<< RFB.constructor"); + Log.Debug("<< RFB.constructor"); }; -(function() { - var _ = Util.Localisation.get; +RFB.prototype = { + // Public methods + connect: function (host, port, password, path) { + this._rfb_host = host; + this._rfb_port = port; + this._rfb_password = (password !== undefined) ? password : ""; + this._rfb_path = (path !== undefined) ? path : ""; - RFB.prototype = { - // Public methods - connect: function (host, port, password, path) { - this._rfb_host = host; - this._rfb_port = port; - this._rfb_password = (password !== undefined) ? password : ""; - this._rfb_path = (path !== undefined) ? path : ""; + if (!this._rfb_host || !this._rfb_port) { + return this._fail( + _("Must set host and port")); + } - if (!this._rfb_host || !this._rfb_port) { - return this._fail( - _("Must set host and port")); - } + this._rfb_init_state = ''; + this._updateConnectionState('connecting'); + return true; + }, - this._rfb_init_state = ''; - this._updateConnectionState('connecting'); - return true; - }, + disconnect: function () { + this._updateConnectionState('disconnecting'); + this._sock.off('error'); + this._sock.off('message'); + this._sock.off('open'); + }, - disconnect: function () { - this._updateConnectionState('disconnecting'); - this._sock.off('error'); - this._sock.off('message'); - this._sock.off('open'); - }, + sendPassword: function (passwd) { + this._rfb_password = passwd; + setTimeout(this._init_msg.bind(this), 0); + }, - sendPassword: function (passwd) { - this._rfb_password = passwd; - setTimeout(this._init_msg.bind(this), 0); - }, + sendCtrlAltDel: function () { + if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } + Log.Info("Sending Ctrl-Alt-Del"); - sendCtrlAltDel: function () { - if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } - Util.Info("Sending Ctrl-Alt-Del"); + RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 1); + RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 1); + RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 1); + RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 0); + RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 0); + RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 0); + return true; + }, - RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 1); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 1); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 1); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 0); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 0); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 0); - return true; - }, + xvpOp: function (ver, op) { + if (this._rfb_xvp_ver < ver) { return false; } + Log.Info("Sending XVP operation " + op + " (version " + ver + ")"); + this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op)); + return true; + }, - xvpOp: function (ver, op) { - if (this._rfb_xvp_ver < ver) { return false; } - Util.Info("Sending XVP operation " + op + " (version " + ver + ")"); - this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op)); - return true; - }, + xvpShutdown: function () { + return this.xvpOp(1, 2); + }, - xvpShutdown: function () { - return this.xvpOp(1, 2); - }, + xvpReboot: function () { + return this.xvpOp(1, 3); + }, - xvpReboot: function () { - return this.xvpOp(1, 3); - }, + xvpReset: function () { + return this.xvpOp(1, 4); + }, - xvpReset: function () { - return this.xvpOp(1, 4); - }, + // Send a key press. If 'down' is not specified then send a down key + // followed by an up key. + sendKey: function (keysym, down) { + if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } + if (typeof down !== 'undefined') { + Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym); + RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0); + } else { + Log.Info("Sending keysym (down + up): " + keysym); + RFB.messages.keyEvent(this._sock, keysym, 1); + RFB.messages.keyEvent(this._sock, keysym, 0); + } + return true; + }, - // Send a key press. If 'down' is not specified then send a down key - // followed by an up key. - sendKey: function (keysym, down) { - if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } - if (typeof down !== 'undefined') { - Util.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym); - RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0); - } else { - Util.Info("Sending keysym (down + up): " + keysym); - RFB.messages.keyEvent(this._sock, keysym, 1); - RFB.messages.keyEvent(this._sock, keysym, 0); - } - return true; - }, - - clipboardPasteFrom: function (text) { - if (this._rfb_connection_state !== 'connected' || this._view_only) { - return; - } - RFB.messages.clientCutText(this._sock, text); - }, - - // Requests a change of remote desktop size. This message is an extension - // and may only be sent if we have received an ExtendedDesktopSize message - requestDesktopSize: function (width, height) { - if (this._rfb_connection_state !== 'connected' || - this._view_only) { - return false; - } - - if (this._supportsSetDesktopSize) { - RFB.messages.setDesktopSize(this._sock, width, height, - this._screen_id, this._screen_flags); - this._sock.flush(); - return true; - } else { - return false; - } - }, - - - // Private methods - - _connect: function () { - Util.Debug(">> RFB.connect"); - this._init_vars(); - - var uri; - if (typeof UsingSocketIO !== 'undefined') { - uri = 'http'; - } else { - uri = this._encrypt ? 'wss' : 'ws'; - } - - uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path; - Util.Info("connecting to " + uri); - - try { - // WebSocket.onopen transitions to the RFB init states - this._sock.open(uri, this._wsProtocols); - } catch (e) { - if (e.name === 'SyntaxError') { - this._fail("Invalid host or port value given", e); - } else { - this._fail("Error while connecting", e); - } - } - - Util.Debug("<< RFB.connect"); - }, - - _disconnect: function () { - Util.Debug(">> RFB.disconnect"); - this._cleanup(); - this._sock.close(); - this._print_stats(); - Util.Debug("<< RFB.disconnect"); - }, - - _init_vars: function () { - // reset state - this._FBU.rects = 0; - this._FBU.subrects = 0; // RRE and HEXTILE - this._FBU.lines = 0; // RAW - this._FBU.tiles = 0; // HEXTILE - this._FBU.zlibs = []; // TIGHT zlib encoders - this._mouse_buttonMask = 0; - this._mouse_arr = []; - this._rfb_tightvnc = false; - - // Clear the per connection encoding stats - var i; - for (i = 0; i < this._encodings.length; i++) { - this._encStats[this._encodings[i][1]][0] = 0; - } - - for (i = 0; i < 4; i++) { - this._FBU.zlibs[i] = new Inflator(); - } - }, - - _print_stats: function () { - Util.Info("Encoding stats for this connection:"); - var i, s; - for (i = 0; i < this._encodings.length; i++) { - s = this._encStats[this._encodings[i][1]]; - if (s[0] + s[1] > 0) { - Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects"); - } - } - - Util.Info("Encoding stats since page load:"); - for (i = 0; i < this._encodings.length; i++) { - s = this._encStats[this._encodings[i][1]]; - Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects"); - } - }, - - _cleanup: function () { - if (!this._view_only) { this._keyboard.ungrab(); } - if (!this._view_only) { this._mouse.ungrab(); } - this._display.defaultCursor(); - if (Util.get_logging() !== 'debug') { - // Show noVNC logo on load and when disconnected, unless in - // debug mode - this._display.clear(); - } - }, - - /* - * Connection states: - * connecting - * connected - * disconnecting - * disconnected - permanent state - */ - _updateConnectionState: function (state) { - var oldstate = this._rfb_connection_state; - - if (state === oldstate) { - Util.Debug("Already in state '" + state + "', ignoring"); - return; - } - - // The 'disconnected' state is permanent for each RFB object - if (oldstate === 'disconnected') { - Util.Error("Tried changing state of a disconnected RFB object"); - return; - } - - // Ensure proper transitions before doing anything - switch (state) { - case 'connected': - if (oldstate !== 'connecting') { - Util.Error("Bad transition to connected state, " + - "previous connection state: " + oldstate); - return; - } - break; - - case 'disconnected': - if (oldstate !== 'disconnecting') { - Util.Error("Bad transition to disconnected state, " + - "previous connection state: " + oldstate); - return; - } - break; - - case 'connecting': - if (oldstate !== '') { - Util.Error("Bad transition to connecting state, " + - "previous connection state: " + oldstate); - return; - } - break; - - case 'disconnecting': - if (oldstate !== 'connected' && oldstate !== 'connecting') { - Util.Error("Bad transition to disconnecting state, " + - "previous connection state: " + oldstate); - return; - } - break; - - default: - Util.Error("Unknown connection state: " + state); - return; - } - - // State change actions - - this._rfb_connection_state = state; - this._onUpdateState(this, state, oldstate); - - var smsg = "New state '" + state + "', was '" + oldstate + "'."; - Util.Debug(smsg); - - if (this._disconnTimer && state !== 'disconnecting') { - Util.Debug("Clearing disconnect timer"); - clearTimeout(this._disconnTimer); - this._disconnTimer = null; - - // make sure we don't get a double event - this._sock.off('close'); - } - - switch (state) { - case 'disconnected': - // Call onDisconnected callback after onUpdateState since - // we don't know if the UI only displays the latest message - if (this._rfb_disconnect_reason !== "") { - this._onDisconnected(this, this._rfb_disconnect_reason); - } else { - // No reason means clean disconnect - this._onDisconnected(this); - } - break; - - case 'connecting': - this._connect(); - break; - - case 'disconnecting': - this._disconnect(); - - this._disconnTimer = setTimeout(function () { - this._rfb_disconnect_reason = _("Disconnect timeout"); - this._updateConnectionState('disconnected'); - }.bind(this), this._disconnectTimeout * 1000); - break; - } - }, - - /* Print errors and disconnect - * - * The optional parameter 'details' is used for information that - * should be logged but not sent to the user interface. - */ - _fail: function (msg, details) { - var fullmsg = msg; - if (typeof details !== 'undefined') { - fullmsg = msg + " (" + details + ")"; - } - switch (this._rfb_connection_state) { - case 'disconnecting': - Util.Error("Failed when disconnecting: " + fullmsg); - break; - case 'connected': - Util.Error("Failed while connected: " + fullmsg); - break; - case 'connecting': - Util.Error("Failed when connecting: " + fullmsg); - break; - default: - Util.Error("RFB failure: " + fullmsg); - break; - } - this._rfb_disconnect_reason = msg; //This is sent to the UI - - // Transition to disconnected without waiting for socket to close - this._updateConnectionState('disconnecting'); - this._updateConnectionState('disconnected'); + clipboardPasteFrom: function (text) { + if (this._rfb_connection_state !== 'connected' || this._view_only) { return; } + RFB.messages.clientCutText(this._sock, text); + }, + // Requests a change of remote desktop size. This message is an extension + // and may only be sent if we have received an ExtendedDesktopSize message + requestDesktopSize: function (width, height) { + if (this._rfb_connection_state !== 'connected' || + this._view_only) { return false; - }, + } - /* - * Send a notification to the UI. Valid levels are: - * 'normal'|'warn'|'error' - * - * NOTE: Options could be added in the future. - * NOTE: If this function is called multiple times, remember that the - * interface could be only showing the latest notification. - */ - _notification: function(msg, level, options) { - switch (level) { - case 'normal': - case 'warn': - case 'error': - Util.Debug("Notification[" + level + "]:" + msg); - break; - default: - Util.Error("Invalid notification level: " + level); - return; - } + if (this._supportsSetDesktopSize) { + RFB.messages.setDesktopSize(this._sock, width, height, + this._screen_id, this._screen_flags); + this._sock.flush(); + return true; + } else { + return false; + } + }, - if (options) { - this._onNotification(this, msg, level, options); + + // Private methods + + _connect: function () { + Log.Debug(">> RFB.connect"); + this._init_vars(); + + var uri; + if (typeof UsingSocketIO !== 'undefined') { + uri = 'http'; + } else { + uri = this._encrypt ? 'wss' : 'ws'; + } + + uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path; + Log.Info("connecting to " + uri); + + try { + // WebSocket.onopen transitions to the RFB init states + this._sock.open(uri, this._wsProtocols); + } catch (e) { + if (e.name === 'SyntaxError') { + this._fail("Invalid host or port value given", e); } else { - this._onNotification(this, msg, level); + this._fail("Error while connecting", e); } - }, + } - _handle_message: function () { - if (this._sock.rQlen() === 0) { - Util.Warn("handle_message called on an empty receive queue"); + Log.Debug("<< RFB.connect"); + }, + + _disconnect: function () { + Log.Debug(">> RFB.disconnect"); + this._cleanup(); + this._sock.close(); + this._print_stats(); + Log.Debug("<< RFB.disconnect"); + }, + + _init_vars: function () { + // reset state + this._FBU.rects = 0; + this._FBU.subrects = 0; // RRE and HEXTILE + this._FBU.lines = 0; // RAW + this._FBU.tiles = 0; // HEXTILE + this._FBU.zlibs = []; // TIGHT zlib encoders + this._mouse_buttonMask = 0; + this._mouse_arr = []; + this._rfb_tightvnc = false; + + // Clear the per connection encoding stats + var i; + for (i = 0; i < this._encodings.length; i++) { + this._encStats[this._encodings[i][1]][0] = 0; + } + + for (i = 0; i < 4; i++) { + this._FBU.zlibs[i] = new Inflator(); + } + }, + + _print_stats: function () { + Log.Info("Encoding stats for this connection:"); + var i, s; + for (i = 0; i < this._encodings.length; i++) { + s = this._encStats[this._encodings[i][1]]; + if (s[0] + s[1] > 0) { + Log.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects"); + } + } + + Log.Info("Encoding stats since page load:"); + for (i = 0; i < this._encodings.length; i++) { + s = this._encStats[this._encodings[i][1]]; + Log.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects"); + } + }, + + _cleanup: function () { + if (!this._view_only) { this._keyboard.ungrab(); } + if (!this._view_only) { this._mouse.ungrab(); } + this._display.defaultCursor(); + if (Log.get_logging() !== 'debug') { + // Show noVNC logo on load and when disconnected, unless in + // debug mode + this._display.clear(); + } + }, + + /* + * Connection states: + * connecting + * connected + * disconnecting + * disconnected - permanent state + */ + _updateConnectionState: function (state) { + var oldstate = this._rfb_connection_state; + + if (state === oldstate) { + Log.Debug("Already in state '" + state + "', ignoring"); + return; + } + + // The 'disconnected' state is permanent for each RFB object + if (oldstate === 'disconnected') { + Log.Error("Tried changing state of a disconnected RFB object"); + return; + } + + // Ensure proper transitions before doing anything + switch (state) { + case 'connected': + if (oldstate !== 'connecting') { + Log.Error("Bad transition to connected state, " + + "previous connection state: " + oldstate); + return; + } + break; + + case 'disconnected': + if (oldstate !== 'disconnecting') { + Log.Error("Bad transition to disconnected state, " + + "previous connection state: " + oldstate); + return; + } + break; + + case 'connecting': + if (oldstate !== '') { + Log.Error("Bad transition to connecting state, " + + "previous connection state: " + oldstate); + return; + } + break; + + case 'disconnecting': + if (oldstate !== 'connected' && oldstate !== 'connecting') { + Log.Error("Bad transition to disconnecting state, " + + "previous connection state: " + oldstate); + return; + } + break; + + default: + Log.Error("Unknown connection state: " + state); return; - } + } - switch (this._rfb_connection_state) { - case 'disconnected': - Util.Error("Got data while disconnected"); - break; - case 'connected': - while (true) { - if (this._flushing) { - break; - } - if (!this._normal_msg()) { - break; - } - if (this._sock.rQlen() === 0) { - break; - } - } - break; - default: - this._init_msg(); - break; - } - }, + // State change actions - _handleKeyPress: function (keyevent) { - if (this._view_only) { return; } // View only, skip keyboard, events + this._rfb_connection_state = state; + this._onUpdateState(this, state, oldstate); - var down = (keyevent.type == 'keydown'); - if (this._qemuExtKeyEventSupported) { - var scancode = XtScancode[keyevent.code]; - if (scancode) { - var keysym = keyevent.keysym; - RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode); + var smsg = "New state '" + state + "', was '" + oldstate + "'."; + Log.Debug(smsg); + + if (this._disconnTimer && state !== 'disconnecting') { + Log.Debug("Clearing disconnect timer"); + clearTimeout(this._disconnTimer); + this._disconnTimer = null; + + // make sure we don't get a double event + this._sock.off('close'); + } + + switch (state) { + case 'disconnected': + // Call onDisconnected callback after onUpdateState since + // we don't know if the UI only displays the latest message + if (this._rfb_disconnect_reason !== "") { + this._onDisconnected(this, this._rfb_disconnect_reason); } else { - Util.Error('Unable to find a xt scancode for code = ' + keyevent.code); + // No reason means clean disconnect + this._onDisconnected(this); } - } else { - keysym = keyevent.keysym.keysym; - RFB.messages.keyEvent(this._sock, keysym, down); - } - }, + break; - _handleMouseButton: function (x, y, down, bmask) { - if (down) { - this._mouse_buttonMask |= bmask; - } else { - this._mouse_buttonMask ^= bmask; - } + case 'connecting': + this._connect(); + break; - if (this._viewportDrag) { - if (down && !this._viewportDragging) { - this._viewportDragging = true; - this._viewportDragPos = {'x': x, 'y': y}; + case 'disconnecting': + this._disconnect(); - // Skip sending mouse events - return; - } else { - this._viewportDragging = false; + this._disconnTimer = setTimeout(function () { + this._rfb_disconnect_reason = _("Disconnect timeout"); + this._updateConnectionState('disconnected'); + }.bind(this), this._disconnectTimeout * 1000); + break; + } + }, - // If the viewport didn't actually move, then treat as a mouse click event - // Send the button down event here, as the button up event is sent at the end of this function - if (!this._viewportHasMoved && !this._view_only) { - RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask); + /* Print errors and disconnect + * + * The optional parameter 'details' is used for information that + * should be logged but not sent to the user interface. + */ + _fail: function (msg, details) { + var fullmsg = msg; + if (typeof details !== 'undefined') { + fullmsg = msg + " (" + details + ")"; + } + switch (this._rfb_connection_state) { + case 'disconnecting': + Log.Error("Failed when disconnecting: " + fullmsg); + break; + case 'connected': + Log.Error("Failed while connected: " + fullmsg); + break; + case 'connecting': + Log.Error("Failed when connecting: " + fullmsg); + break; + default: + Log.Error("RFB failure: " + fullmsg); + break; + } + this._rfb_disconnect_reason = msg; //This is sent to the UI + + // Transition to disconnected without waiting for socket to close + this._updateConnectionState('disconnecting'); + this._updateConnectionState('disconnected'); + + return false; + }, + + /* + * Send a notification to the UI. Valid levels are: + * 'normal'|'warn'|'error' + * + * NOTE: Options could be added in the future. + * NOTE: If this function is called multiple times, remember that the + * interface could be only showing the latest notification. + */ + _notification: function(msg, level, options) { + switch (level) { + case 'normal': + case 'warn': + case 'error': + Log.Debug("Notification[" + level + "]:" + msg); + break; + default: + Log.Error("Invalid notification level: " + level); + return; + } + + if (options) { + this._onNotification(this, msg, level, options); + } else { + this._onNotification(this, msg, level); + } + }, + + _handle_message: function () { + if (this._sock.rQlen() === 0) { + Log.Warn("handle_message called on an empty receive queue"); + return; + } + + switch (this._rfb_connection_state) { + case 'disconnected': + Log.Error("Got data while disconnected"); + break; + case 'connected': + while (true) { + if (this._flushing) { + break; + } + if (!this._normal_msg()) { + break; + } + if (this._sock.rQlen() === 0) { + break; } - this._viewportHasMoved = false; } + break; + default: + this._init_msg(); + break; + } + }, + + _handleKeyPress: function (keyevent) { + if (this._view_only) { return; } // View only, skip keyboard, events + + var down = (keyevent.type == 'keydown'); + if (this._qemuExtKeyEventSupported) { + var scancode = XtScancode[keyevent.code]; + if (scancode) { + var keysym = keyevent.keysym; + RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode); + } else { + Log.Error('Unable to find a xt scancode for code = ' + keyevent.code); } + } else { + keysym = keyevent.keysym.keysym; + RFB.messages.keyEvent(this._sock, keysym, down); + } + }, - if (this._view_only) { return; } // View only, skip mouse events + _handleMouseButton: function (x, y, down, bmask) { + if (down) { + this._mouse_buttonMask |= bmask; + } else { + this._mouse_buttonMask ^= bmask; + } - if (this._rfb_connection_state !== 'connected') { return; } - RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); - }, - - _handleMouseMove: function (x, y) { - if (this._viewportDragging) { - var deltaX = this._viewportDragPos.x - x; - var deltaY = this._viewportDragPos.y - y; - - // The goal is to trigger on a certain physical width, the - // devicePixelRatio brings us a bit closer but is not optimal. - var dragThreshold = 10 * (window.devicePixelRatio || 1); - - if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold || - Math.abs(deltaY) > dragThreshold)) { - this._viewportHasMoved = true; - - this._viewportDragPos = {'x': x, 'y': y}; - this._display.viewportChangePos(deltaX, deltaY); - } + if (this._viewportDrag) { + if (down && !this._viewportDragging) { + this._viewportDragging = true; + this._viewportDragPos = {'x': x, 'y': y}; // Skip sending mouse events return; - } - - if (this._view_only) { return; } // View only, skip mouse events - - if (this._rfb_connection_state !== 'connected') { return; } - RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); - }, - - // Message Handlers - - _negotiate_protocol_version: function () { - if (this._sock.rQlen() < 12) { - return this._fail("Error while negotiating with server", - "Incomplete protocol version"); - } - - var sversion = this._sock.rQshiftStr(12).substr(4, 7); - Util.Info("Server ProtocolVersion: " + sversion); - var is_repeater = 0; - switch (sversion) { - case "000.000": // UltraVNC repeater - is_repeater = 1; - break; - case "003.003": - case "003.006": // UltraVNC - case "003.889": // Apple Remote Desktop - this._rfb_version = 3.3; - break; - case "003.007": - this._rfb_version = 3.7; - break; - case "003.008": - case "004.000": // Intel AMT KVM - case "004.001": // RealVNC 4.6 - case "005.000": // RealVNC 5.3 - this._rfb_version = 3.8; - break; - default: - return this._fail("Unsupported server", - "Invalid server version: " + sversion); - } - - if (is_repeater) { - var repeaterID = this._repeaterID; - while (repeaterID.length < 250) { - repeaterID += "\0"; - } - this._sock.send_string(repeaterID); - return true; - } - - if (this._rfb_version > this._rfb_max_version) { - this._rfb_version = this._rfb_max_version; - } - - var cversion = "00" + parseInt(this._rfb_version, 10) + - ".00" + ((this._rfb_version * 10) % 10); - this._sock.send_string("RFB " + cversion + "\n"); - Util.Debug('Sent ProtocolVersion: ' + cversion); - - this._rfb_init_state = 'Security'; - }, - - _negotiate_security: function () { - if (this._rfb_version >= 3.7) { - // Server sends supported list, client decides - var num_types = this._sock.rQshift8(); - if (this._sock.rQwait("security type", num_types, 1)) { return false; } - - if (num_types === 0) { - var strlen = this._sock.rQshift32(); - var reason = this._sock.rQshiftStr(strlen); - return this._fail("Error while negotiating with server", - "Security failure: " + reason); - } - - var types = this._sock.rQshiftBytes(num_types); - Util.Debug("Server security types: " + types); - - // Polyfill since IE and PhantomJS doesn't have - // TypedArray.includes() - function includes(item, array) { - for (var i = 0; i < array.length; i++) { - if (array[i] === item) { - return true; - } - } - return false; - } - - // Look for each auth in preferred order - this._rfb_auth_scheme = 0; - if (includes(1, types)) { - this._rfb_auth_scheme = 1; // None - } else if (includes(22, types)) { - this._rfb_auth_scheme = 22; // XVP - } else if (includes(16, types)) { - this._rfb_auth_scheme = 16; // Tight - } else if (includes(2, types)) { - this._rfb_auth_scheme = 2; // VNC Auth - } else { - return this._fail("Unsupported server", - "Unsupported security types: " + types); - } - - this._sock.send([this._rfb_auth_scheme]); } else { - // Server decides - if (this._sock.rQwait("security scheme", 4)) { return false; } - this._rfb_auth_scheme = this._sock.rQshift32(); - } + this._viewportDragging = false; - this._rfb_init_state = 'Authentication'; - Util.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme); - - return this._init_msg(); // jump to authentication - }, - - // authentication - _negotiate_xvp_auth: function () { - var xvp_sep = this._xvp_password_sep; - var xvp_auth = this._rfb_password.split(xvp_sep); - if (xvp_auth.length < 3) { - var msg = 'XVP credentials required (user' + xvp_sep + - 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password; - this._onPasswordRequired(this, msg); - return false; - } - - var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) + - String.fromCharCode(xvp_auth[1].length) + - xvp_auth[0] + - xvp_auth[1]; - this._sock.send_string(xvp_auth_str); - this._rfb_password = xvp_auth.slice(2).join(xvp_sep); - this._rfb_auth_scheme = 2; - return this._negotiate_authentication(); - }, - - _negotiate_std_vnc_auth: function () { - if (this._rfb_password.length === 0) { - this._onPasswordRequired(this); - return false; - } - - if (this._sock.rQwait("auth challenge", 16)) { return false; } - - // TODO(directxman12): make genDES not require an Array - var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16)); - var response = RFB.genDES(this._rfb_password, challenge); - this._sock.send(response); - this._rfb_init_state = "SecurityResult"; - return true; - }, - - _negotiate_tight_tunnels: function (numTunnels) { - var clientSupportedTunnelTypes = { - 0: { vendor: 'TGHT', signature: 'NOTUNNEL' } - }; - var serverSupportedTunnelTypes = {}; - // receive tunnel capabilities - for (var i = 0; i < numTunnels; i++) { - var cap_code = this._sock.rQshift32(); - var cap_vendor = this._sock.rQshiftStr(4); - var cap_signature = this._sock.rQshiftStr(8); - serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature }; - } - - // choose the notunnel type - if (serverSupportedTunnelTypes[0]) { - if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor || - serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) { - return this._fail("Unsupported server", - "Client's tunnel type had the incorrect " + - "vendor or signature"); + // If the viewport didn't actually move, then treat as a mouse click event + // Send the button down event here, as the button up event is sent at the end of this function + if (!this._viewportHasMoved && !this._view_only) { + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask); } - this._sock.send([0, 0, 0, 0]); // use NOTUNNEL - return false; // wait until we receive the sub auth count to continue + this._viewportHasMoved = false; + } + } + + if (this._view_only) { return; } // View only, skip mouse events + + if (this._rfb_connection_state !== 'connected') { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); + }, + + _handleMouseMove: function (x, y) { + if (this._viewportDragging) { + var deltaX = this._viewportDragPos.x - x; + var deltaY = this._viewportDragPos.y - y; + + // The goal is to trigger on a certain physical width, the + // devicePixelRatio brings us a bit closer but is not optimal. + var dragThreshold = 10 * (window.devicePixelRatio || 1); + + if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold || + Math.abs(deltaY) > dragThreshold)) { + this._viewportHasMoved = true; + + this._viewportDragPos = {'x': x, 'y': y}; + this._display.viewportChangePos(deltaX, deltaY); + } + + // Skip sending mouse events + return; + } + + if (this._view_only) { return; } // View only, skip mouse events + + if (this._rfb_connection_state !== 'connected') { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); + }, + + // Message Handlers + + _negotiate_protocol_version: function () { + if (this._sock.rQlen() < 12) { + return this._fail("Error while negotiating with server", + "Incomplete protocol version"); + } + + var sversion = this._sock.rQshiftStr(12).substr(4, 7); + Log.Info("Server ProtocolVersion: " + sversion); + var is_repeater = 0; + switch (sversion) { + case "000.000": // UltraVNC repeater + is_repeater = 1; + break; + case "003.003": + case "003.006": // UltraVNC + case "003.889": // Apple Remote Desktop + this._rfb_version = 3.3; + break; + case "003.007": + this._rfb_version = 3.7; + break; + case "003.008": + case "004.000": // Intel AMT KVM + case "004.001": // RealVNC 4.6 + case "005.000": // RealVNC 5.3 + this._rfb_version = 3.8; + break; + default: + return this._fail("Unsupported server", + "Invalid server version: " + sversion); + } + + if (is_repeater) { + var repeaterID = this._repeaterID; + while (repeaterID.length < 250) { + repeaterID += "\0"; + } + this._sock.send_string(repeaterID); + return true; + } + + if (this._rfb_version > this._rfb_max_version) { + this._rfb_version = this._rfb_max_version; + } + + var cversion = "00" + parseInt(this._rfb_version, 10) + + ".00" + ((this._rfb_version * 10) % 10); + this._sock.send_string("RFB " + cversion + "\n"); + Log.Debug('Sent ProtocolVersion: ' + cversion); + + this._rfb_init_state = 'Security'; + }, + + _negotiate_security: function () { + // Polyfill since IE and PhantomJS doesn't have + // TypedArray.includes() + function includes(item, array) { + for (var i = 0; i < array.length; i++) { + if (array[i] === item) { + return true; + } + } + return false; + } + + if (this._rfb_version >= 3.7) { + // Server sends supported list, client decides + var num_types = this._sock.rQshift8(); + if (this._sock.rQwait("security type", num_types, 1)) { return false; } + + if (num_types === 0) { + var strlen = this._sock.rQshift32(); + var reason = this._sock.rQshiftStr(strlen); + return this._fail("Error while negotiating with server", + "Security failure: " + reason); + } + + var types = this._sock.rQshiftBytes(num_types); + Log.Debug("Server security types: " + types); + + // Look for each auth in preferred order + this._rfb_auth_scheme = 0; + if (includes(1, types)) { + this._rfb_auth_scheme = 1; // None + } else if (includes(22, types)) { + this._rfb_auth_scheme = 22; // XVP + } else if (includes(16, types)) { + this._rfb_auth_scheme = 16; // Tight + } else if (includes(2, types)) { + this._rfb_auth_scheme = 2; // VNC Auth } else { return this._fail("Unsupported server", - "Server wanted tunnels, but doesn't support " + - "the notunnel type"); - } - }, - - _negotiate_tight_auth: function () { - if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation - if (this._sock.rQwait("num tunnels", 4)) { return false; } - var numTunnels = this._sock.rQshift32(); - if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; } - - this._rfb_tightvnc = true; - - if (numTunnels > 0) { - this._negotiate_tight_tunnels(numTunnels); - return false; // wait until we receive the sub auth to continue - } + "Unsupported security types: " + types); } - // second pass, do the sub-auth negotiation - if (this._sock.rQwait("sub auth count", 4)) { return false; } - var subAuthCount = this._sock.rQshift32(); - if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected - this._rfb_init_state = 'SecurityResult'; - return true; + this._sock.send([this._rfb_auth_scheme]); + } else { + // Server decides + if (this._sock.rQwait("security scheme", 4)) { return false; } + this._rfb_auth_scheme = this._sock.rQshift32(); + } + + this._rfb_init_state = 'Authentication'; + Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme); + + return this._init_msg(); // jump to authentication + }, + + // authentication + _negotiate_xvp_auth: function () { + var xvp_sep = this._xvp_password_sep; + var xvp_auth = this._rfb_password.split(xvp_sep); + if (xvp_auth.length < 3) { + var msg = 'XVP credentials required (user' + xvp_sep + + 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password; + this._onPasswordRequired(this, msg); + return false; + } + + var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) + + String.fromCharCode(xvp_auth[1].length) + + xvp_auth[0] + + xvp_auth[1]; + this._sock.send_string(xvp_auth_str); + this._rfb_password = xvp_auth.slice(2).join(xvp_sep); + this._rfb_auth_scheme = 2; + return this._negotiate_authentication(); + }, + + _negotiate_std_vnc_auth: function () { + if (this._rfb_password.length === 0) { + this._onPasswordRequired(this); + return false; + } + + if (this._sock.rQwait("auth challenge", 16)) { return false; } + + // TODO(directxman12): make genDES not require an Array + var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16)); + var response = RFB.genDES(this._rfb_password, challenge); + this._sock.send(response); + this._rfb_init_state = "SecurityResult"; + return true; + }, + + _negotiate_tight_tunnels: function (numTunnels) { + var clientSupportedTunnelTypes = { + 0: { vendor: 'TGHT', signature: 'NOTUNNEL' } + }; + var serverSupportedTunnelTypes = {}; + // receive tunnel capabilities + for (var i = 0; i < numTunnels; i++) { + var cap_code = this._sock.rQshift32(); + var cap_vendor = this._sock.rQshiftStr(4); + var cap_signature = this._sock.rQshiftStr(8); + serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature }; + } + + // choose the notunnel type + if (serverSupportedTunnelTypes[0]) { + if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor || + serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) { + return this._fail("Unsupported server", + "Client's tunnel type had the incorrect " + + "vendor or signature"); } - - if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; } - - var clientSupportedTypes = { - 'STDVNOAUTH__': 1, - 'STDVVNCAUTH_': 2 - }; - - var serverSupportedTypes = []; - - for (var i = 0; i < subAuthCount; i++) { - var capNum = this._sock.rQshift32(); - var capabilities = this._sock.rQshiftStr(12); - serverSupportedTypes.push(capabilities); - } - - for (var authType in clientSupportedTypes) { - if (serverSupportedTypes.indexOf(authType) != -1) { - this._sock.send([0, 0, 0, clientSupportedTypes[authType]]); - - switch (authType) { - case 'STDVNOAUTH__': // no auth - this._rfb_init_state = 'SecurityResult'; - return true; - case 'STDVVNCAUTH_': // VNC auth - this._rfb_auth_scheme = 2; - return this._init_msg(); - default: - return this._fail("Unsupported server", - "Unsupported tiny auth scheme: " + - authType); - } - } - } - + this._sock.send([0, 0, 0, 0]); // use NOTUNNEL + return false; // wait until we receive the sub auth count to continue + } else { return this._fail("Unsupported server", - "No supported sub-auth types!"); - }, + "Server wanted tunnels, but doesn't support " + + "the notunnel type"); + } + }, - _negotiate_authentication: function () { - switch (this._rfb_auth_scheme) { - case 0: // connection failed - if (this._sock.rQwait("auth reason", 4)) { return false; } - var strlen = this._sock.rQshift32(); - var reason = this._sock.rQshiftStr(strlen); - return this._fail("Authentication failure", reason); + _negotiate_tight_auth: function () { + if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation + if (this._sock.rQwait("num tunnels", 4)) { return false; } + var numTunnels = this._sock.rQshift32(); + if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; } - case 1: // no auth - if (this._rfb_version >= 3.8) { + this._rfb_tightvnc = true; + + if (numTunnels > 0) { + this._negotiate_tight_tunnels(numTunnels); + return false; // wait until we receive the sub auth to continue + } + } + + // second pass, do the sub-auth negotiation + if (this._sock.rQwait("sub auth count", 4)) { return false; } + var subAuthCount = this._sock.rQshift32(); + if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected + this._rfb_init_state = 'SecurityResult'; + return true; + } + + if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; } + + var clientSupportedTypes = { + 'STDVNOAUTH__': 1, + 'STDVVNCAUTH_': 2 + }; + + var serverSupportedTypes = []; + + for (var i = 0; i < subAuthCount; i++) { + var capNum = this._sock.rQshift32(); + var capabilities = this._sock.rQshiftStr(12); + serverSupportedTypes.push(capabilities); + } + + for (var authType in clientSupportedTypes) { + if (serverSupportedTypes.indexOf(authType) != -1) { + this._sock.send([0, 0, 0, clientSupportedTypes[authType]]); + + switch (authType) { + case 'STDVNOAUTH__': // no auth this._rfb_init_state = 'SecurityResult'; return true; - } - this._rfb_init_state = 'ClientInitialisation'; - return this._init_msg(); - - case 22: // XVP auth - return this._negotiate_xvp_auth(); - - case 2: // VNC authentication - return this._negotiate_std_vnc_auth(); - - case 16: // TightVNC Security Type - return this._negotiate_tight_auth(); - - default: - return this._fail("Unsupported server", - "Unsupported auth scheme: " + - this._rfb_auth_scheme); + case 'STDVVNCAUTH_': // VNC auth + this._rfb_auth_scheme = 2; + return this._init_msg(); + default: + return this._fail("Unsupported server", + "Unsupported tiny auth scheme: " + + authType); + } } - }, + } - _handle_security_result: function () { - if (this._sock.rQwait('VNC auth response ', 4)) { return false; } - switch (this._sock.rQshift32()) { - case 0: // OK - this._rfb_init_state = 'ClientInitialisation'; - Util.Debug('Authentication OK'); - return this._init_msg(); - case 1: // failed - if (this._rfb_version >= 3.8) { - var length = this._sock.rQshift32(); - if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; } - var reason = this._sock.rQshiftStr(length); - return this._fail("Authentication failure", reason); - } else { - return this._fail("Authentication failure"); - } - return false; - case 2: - return this._fail("Too many authentication attempts"); - default: - return this._fail("Unsupported server", - "Unknown SecurityResult"); - } - }, + return this._fail("Unsupported server", + "No supported sub-auth types!"); + }, - _negotiate_server_init: function () { - if (this._sock.rQwait("server initialization", 24)) { return false; } + _negotiate_authentication: function () { + switch (this._rfb_auth_scheme) { + case 0: // connection failed + if (this._sock.rQwait("auth reason", 4)) { return false; } + var strlen = this._sock.rQshift32(); + var reason = this._sock.rQshiftStr(strlen); + return this._fail("Authentication failure", reason); - /* Screen size */ - this._fb_width = this._sock.rQshift16(); - this._fb_height = this._sock.rQshift16(); - this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); + case 1: // no auth + if (this._rfb_version >= 3.8) { + this._rfb_init_state = 'SecurityResult'; + return true; + } + this._rfb_init_state = 'ClientInitialisation'; + return this._init_msg(); - /* PIXEL_FORMAT */ - var bpp = this._sock.rQshift8(); - var depth = this._sock.rQshift8(); - var big_endian = this._sock.rQshift8(); - var true_color = this._sock.rQshift8(); + case 22: // XVP auth + return this._negotiate_xvp_auth(); - var red_max = this._sock.rQshift16(); - var green_max = this._sock.rQshift16(); - var blue_max = this._sock.rQshift16(); - var red_shift = this._sock.rQshift8(); - var green_shift = this._sock.rQshift8(); - var blue_shift = this._sock.rQshift8(); - this._sock.rQskipBytes(3); // padding + case 2: // VNC authentication + return this._negotiate_std_vnc_auth(); - // NB(directxman12): we don't want to call any callbacks or print messages until - // *after* we're past the point where we could backtrack + case 16: // TightVNC Security Type + return this._negotiate_tight_auth(); - /* Connection name/title */ - var name_length = this._sock.rQshift32(); - if (this._sock.rQwait('server init name', name_length, 24)) { return false; } - this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length)); + default: + return this._fail("Unsupported server", + "Unsupported auth scheme: " + + this._rfb_auth_scheme); + } + }, - if (this._rfb_tightvnc) { - if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; } - // In TightVNC mode, ServerInit message is extended - var numServerMessages = this._sock.rQshift16(); - var numClientMessages = this._sock.rQshift16(); - var numEncodings = this._sock.rQshift16(); - this._sock.rQskipBytes(2); // padding + _handle_security_result: function () { + if (this._sock.rQwait('VNC auth response ', 4)) { return false; } + switch (this._sock.rQshift32()) { + case 0: // OK + this._rfb_init_state = 'ClientInitialisation'; + Log.Debug('Authentication OK'); + return this._init_msg(); + case 1: // failed + if (this._rfb_version >= 3.8) { + var length = this._sock.rQshift32(); + if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; } + var reason = this._sock.rQshiftStr(length); + return this._fail("Authentication failure", reason); + } else { + return this._fail("Authentication failure"); + } + return false; + case 2: + return this._fail("Too many authentication attempts"); + default: + return this._fail("Unsupported server", + "Unknown SecurityResult"); + } + }, - var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16; - if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; } + _negotiate_server_init: function () { + if (this._sock.rQwait("server initialization", 24)) { return false; } - // we don't actually do anything with the capability information that TIGHT sends, - // so we just skip the all of this. + /* Screen size */ + this._fb_width = this._sock.rQshift16(); + this._fb_height = this._sock.rQshift16(); + this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); - // TIGHT server message capabilities - this._sock.rQskipBytes(16 * numServerMessages); + /* PIXEL_FORMAT */ + var bpp = this._sock.rQshift8(); + var depth = this._sock.rQshift8(); + var big_endian = this._sock.rQshift8(); + var true_color = this._sock.rQshift8(); - // TIGHT client message capabilities - this._sock.rQskipBytes(16 * numClientMessages); + var red_max = this._sock.rQshift16(); + var green_max = this._sock.rQshift16(); + var blue_max = this._sock.rQshift16(); + var red_shift = this._sock.rQshift8(); + var green_shift = this._sock.rQshift8(); + var blue_shift = this._sock.rQshift8(); + this._sock.rQskipBytes(3); // padding - // TIGHT encoding capabilities - this._sock.rQskipBytes(16 * numEncodings); - } + // NB(directxman12): we don't want to call any callbacks or print messages until + // *after* we're past the point where we could backtrack - // NB(directxman12): these are down here so that we don't run them multiple times - // if we backtrack - Util.Info("Screen: " + this._fb_width + "x" + this._fb_height + - ", bpp: " + bpp + ", depth: " + depth + - ", big_endian: " + big_endian + - ", true_color: " + true_color + - ", red_max: " + red_max + - ", green_max: " + green_max + - ", blue_max: " + blue_max + - ", red_shift: " + red_shift + - ", green_shift: " + green_shift + - ", blue_shift: " + blue_shift); + /* Connection name/title */ + var name_length = this._sock.rQshift32(); + if (this._sock.rQwait('server init name', name_length, 24)) { return false; } + this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length)); - if (big_endian !== 0) { - Util.Warn("Server native endian is not little endian"); - } + if (this._rfb_tightvnc) { + if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; } + // In TightVNC mode, ServerInit message is extended + var numServerMessages = this._sock.rQshift16(); + var numClientMessages = this._sock.rQshift16(); + var numEncodings = this._sock.rQshift16(); + this._sock.rQskipBytes(2); // padding - if (red_shift !== 16) { - Util.Warn("Server native red-shift is not 16"); - } + var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16; + if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; } - if (blue_shift !== 0) { - Util.Warn("Server native blue-shift is not 0"); - } + // we don't actually do anything with the capability information that TIGHT sends, + // so we just skip the all of this. - // we're past the point where we could backtrack, so it's safe to call this - this._onDesktopName(this, this._fb_name); + // TIGHT server message capabilities + this._sock.rQskipBytes(16 * numServerMessages); - if (this._true_color && this._fb_name === "Intel(r) AMT KVM") { - Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color"); - this._true_color = false; - } + // TIGHT client message capabilities + this._sock.rQskipBytes(16 * numClientMessages); - this._display.set_true_color(this._true_color); - this._display.resize(this._fb_width, this._fb_height); - this._onFBResize(this, this._fb_width, this._fb_height); + // TIGHT encoding capabilities + this._sock.rQskipBytes(16 * numEncodings); + } - if (!this._view_only) { this._keyboard.grab(); } - if (!this._view_only) { this._mouse.grab(); } + // NB(directxman12): these are down here so that we don't run them multiple times + // if we backtrack + Log.Info("Screen: " + this._fb_width + "x" + this._fb_height + + ", bpp: " + bpp + ", depth: " + depth + + ", big_endian: " + big_endian + + ", true_color: " + true_color + + ", red_max: " + red_max + + ", green_max: " + green_max + + ", blue_max: " + blue_max + + ", red_shift: " + red_shift + + ", green_shift: " + green_shift + + ", blue_shift: " + blue_shift); - if (this._true_color) { - this._fb_Bpp = 4; - this._fb_depth = 3; - } else { - this._fb_Bpp = 1; - this._fb_depth = 1; - } + if (big_endian !== 0) { + Log.Warn("Server native endian is not little endian"); + } - RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color); - RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color); - RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height); + if (red_shift !== 16) { + Log.Warn("Server native red-shift is not 16"); + } - this._timing.fbu_rt_start = (new Date()).getTime(); - this._timing.pixels = 0; + if (blue_shift !== 0) { + Log.Warn("Server native blue-shift is not 0"); + } - this._updateConnectionState('connected'); - return true; - }, + // we're past the point where we could backtrack, so it's safe to call this + this._onDesktopName(this, this._fb_name); - /* RFB protocol initialization states: - * ProtocolVersion - * Security - * Authentication - * SecurityResult - * ClientInitialization - not triggered by server message - * ServerInitialization + if (this._true_color && this._fb_name === "Intel(r) AMT KVM") { + Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color"); + this._true_color = false; + } + + this._display.set_true_color(this._true_color); + this._display.resize(this._fb_width, this._fb_height); + this._onFBResize(this, this._fb_width, this._fb_height); + + if (!this._view_only) { this._keyboard.grab(); } + if (!this._view_only) { this._mouse.grab(); } + + if (this._true_color) { + this._fb_Bpp = 4; + this._fb_depth = 3; + } else { + this._fb_Bpp = 1; + this._fb_depth = 1; + } + + RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color); + RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color); + RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height); + + this._timing.fbu_rt_start = (new Date()).getTime(); + this._timing.pixels = 0; + + this._updateConnectionState('connected'); + return true; + }, + + /* RFB protocol initialization states: + * ProtocolVersion + * Security + * Authentication + * SecurityResult + * ClientInitialization - not triggered by server message + * ServerInitialization + */ + _init_msg: function () { + switch (this._rfb_init_state) { + case 'ProtocolVersion': + return this._negotiate_protocol_version(); + + case 'Security': + return this._negotiate_security(); + + case 'Authentication': + return this._negotiate_authentication(); + + case 'SecurityResult': + return this._handle_security_result(); + + case 'ClientInitialisation': + this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation + this._rfb_init_state = 'ServerInitialisation'; + return true; + + case 'ServerInitialisation': + return this._negotiate_server_init(); + + default: + return this._fail("Internal error", "Unknown init state: " + + this._rfb_init_state); + } + }, + + _handle_set_colour_map_msg: function () { + Log.Debug("SetColorMapEntries"); + this._sock.rQskip8(); // Padding + + var first_colour = this._sock.rQshift16(); + var num_colours = this._sock.rQshift16(); + if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; } + + for (var c = 0; c < num_colours; c++) { + var red = parseInt(this._sock.rQshift16() / 256, 10); + var green = parseInt(this._sock.rQshift16() / 256, 10); + var blue = parseInt(this._sock.rQshift16() / 256, 10); + this._display.set_colourMap([blue, green, red], first_colour + c); + } + Log.Debug("colourMap: " + this._display.get_colourMap()); + Log.Info("Registered " + num_colours + " colourMap entries"); + + return true; + }, + + _handle_server_cut_text: function () { + Log.Debug("ServerCutText"); + if (this._view_only) { return true; } + + if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; } + this._sock.rQskipBytes(3); // Padding + var length = this._sock.rQshift32(); + if (this._sock.rQwait("ServerCutText", length, 8)) { return false; } + + var text = this._sock.rQshiftStr(length); + this._onClipboard(this, text); + + return true; + }, + + _handle_server_fence_msg: function() { + if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; } + this._sock.rQskipBytes(3); // Padding + var flags = this._sock.rQshift32(); + var length = this._sock.rQshift8(); + + if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; } + + if (length > 64) { + Log.Warn("Bad payload length (" + length + ") in fence response"); + length = 64; + } + + var payload = this._sock.rQshiftStr(length); + + this._supportsFence = true; + + /* + * Fence flags + * + * (1<<0) - BlockBefore + * (1<<1) - BlockAfter + * (1<<2) - SyncNext + * (1<<31) - Request */ - _init_msg: function () { - switch (this._rfb_init_state) { - case 'ProtocolVersion': - return this._negotiate_protocol_version(); - case 'Security': - return this._negotiate_security(); + if (!(flags & (1<<31))) { + return this._fail("Internal error", + "Unexpected fence response"); + } - case 'Authentication': - return this._negotiate_authentication(); + // Filter out unsupported flags + // FIXME: support syncNext + flags &= (1<<0) | (1<<1); - case 'SecurityResult': - return this._handle_security_result(); + // BlockBefore and BlockAfter are automatically handled by + // the fact that we process each incoming message + // synchronuosly. + RFB.messages.clientFence(this._sock, flags, payload); - case 'ClientInitialisation': - this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation - this._rfb_init_state = 'ServerInitialisation'; - return true; + return true; + }, - case 'ServerInitialisation': - return this._negotiate_server_init(); + _handle_xvp_msg: function () { + if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; } + this._sock.rQskip8(); // Padding + var xvp_ver = this._sock.rQshift8(); + var xvp_msg = this._sock.rQshift8(); - default: - return this._fail("Internal error", "Unknown init state: " + - this._rfb_init_state); - } - }, + switch (xvp_msg) { + case 0: // XVP_FAIL + Log.Error("Operation Failed"); + this._notification("XVP Operation Failed", 'error'); + break; + case 1: // XVP_INIT + this._rfb_xvp_ver = xvp_ver; + Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")"); + this._onXvpInit(this._rfb_xvp_ver); + break; + default: + this._fail("Unexpected server message", + "Illegal server XVP message " + xvp_msg); + break; + } - _handle_set_colour_map_msg: function () { - Util.Debug("SetColorMapEntries"); + return true; + }, + + _normal_msg: function () { + var msg_type; + + if (this._FBU.rects > 0) { + msg_type = 0; + } else { + msg_type = this._sock.rQshift8(); + } + + switch (msg_type) { + case 0: // FramebufferUpdate + var ret = this._framebufferUpdate(); + if (ret && !this._enabledContinuousUpdates) { + RFB.messages.fbUpdateRequest(this._sock, true, 0, 0, + this._fb_width, this._fb_height); + } + return ret; + + case 1: // SetColorMapEntries + return this._handle_set_colour_map_msg(); + + case 2: // Bell + Log.Debug("Bell"); + this._onBell(this); + return true; + + case 3: // ServerCutText + return this._handle_server_cut_text(); + + case 150: // EndOfContinuousUpdates + var first = !(this._supportsContinuousUpdates); + this._supportsContinuousUpdates = true; + this._enabledContinuousUpdates = false; + if (first) { + this._enabledContinuousUpdates = true; + this._updateContinuousUpdates(); + Log.Info("Enabling continuous updates."); + } else { + // FIXME: We need to send a framebufferupdaterequest here + // if we add support for turning off continuous updates + } + return true; + + case 248: // ServerFence + return this._handle_server_fence_msg(); + + case 250: // XVP + return this._handle_xvp_msg(); + + default: + this._fail("Unexpected server message", "Type:" + msg_type); + Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30)); + return true; + } + }, + + _onFlush: function() { + this._flushing = false; + // Resume processing + if (this._sock.rQlen() > 0) { + this._handle_message(); + } + }, + + _framebufferUpdate: function () { + var ret = true; + var now; + + if (this._FBU.rects === 0) { + if (this._sock.rQwait("FBU header", 3, 1)) { return false; } this._sock.rQskip8(); // Padding - - var first_colour = this._sock.rQshift16(); - var num_colours = this._sock.rQshift16(); - if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; } - - for (var c = 0; c < num_colours; c++) { - var red = parseInt(this._sock.rQshift16() / 256, 10); - var green = parseInt(this._sock.rQshift16() / 256, 10); - var blue = parseInt(this._sock.rQshift16() / 256, 10); - this._display.set_colourMap([blue, green, red], first_colour + c); - } - Util.Debug("colourMap: " + this._display.get_colourMap()); - Util.Info("Registered " + num_colours + " colourMap entries"); - - return true; - }, - - _handle_server_cut_text: function () { - Util.Debug("ServerCutText"); - if (this._view_only) { return true; } - - if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; } - this._sock.rQskipBytes(3); // Padding - var length = this._sock.rQshift32(); - if (this._sock.rQwait("ServerCutText", length, 8)) { return false; } - - var text = this._sock.rQshiftStr(length); - this._onClipboard(this, text); - - return true; - }, - - _handle_server_fence_msg: function() { - if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; } - this._sock.rQskipBytes(3); // Padding - var flags = this._sock.rQshift32(); - var length = this._sock.rQshift8(); - - if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; } - - if (length > 64) { - Util.Warn("Bad payload length (" + length + ") in fence response"); - length = 64; - } - - var payload = this._sock.rQshiftStr(length); - - this._supportsFence = true; - - /* - * Fence flags - * - * (1<<0) - BlockBefore - * (1<<1) - BlockAfter - * (1<<2) - SyncNext - * (1<<31) - Request - */ - - if (!(flags & (1<<31))) { - return this._fail("Internal error", - "Unexpected fence response"); - } - - // Filter out unsupported flags - // FIXME: support syncNext - flags &= (1<<0) | (1<<1); - - // BlockBefore and BlockAfter are automatically handled by - // the fact that we process each incoming message - // synchronuosly. - RFB.messages.clientFence(this._sock, flags, payload); - - return true; - }, - - _handle_xvp_msg: function () { - if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; } - this._sock.rQskip8(); // Padding - var xvp_ver = this._sock.rQshift8(); - var xvp_msg = this._sock.rQshift8(); - - switch (xvp_msg) { - case 0: // XVP_FAIL - Util.Error("Operation Failed"); - this._notification("XVP Operation Failed", 'error'); - break; - case 1: // XVP_INIT - this._rfb_xvp_ver = xvp_ver; - Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")"); - this._onXvpInit(this._rfb_xvp_ver); - break; - default: - this._fail("Unexpected server message", - "Illegal server XVP message " + xvp_msg); - break; - } - - return true; - }, - - _normal_msg: function () { - var msg_type; - - if (this._FBU.rects > 0) { - msg_type = 0; - } else { - msg_type = this._sock.rQshift8(); - } - - switch (msg_type) { - case 0: // FramebufferUpdate - var ret = this._framebufferUpdate(); - if (ret && !this._enabledContinuousUpdates) { - RFB.messages.fbUpdateRequest(this._sock, true, 0, 0, - this._fb_width, this._fb_height); - } - return ret; - - case 1: // SetColorMapEntries - return this._handle_set_colour_map_msg(); - - case 2: // Bell - Util.Debug("Bell"); - this._onBell(this); - return true; - - case 3: // ServerCutText - return this._handle_server_cut_text(); - - case 150: // EndOfContinuousUpdates - var first = !(this._supportsContinuousUpdates); - this._supportsContinuousUpdates = true; - this._enabledContinuousUpdates = false; - if (first) { - this._enabledContinuousUpdates = true; - this._updateContinuousUpdates(); - Util.Info("Enabling continuous updates."); - } else { - // FIXME: We need to send a framebufferupdaterequest here - // if we add support for turning off continuous updates - } - return true; - - case 248: // ServerFence - return this._handle_server_fence_msg(); - - case 250: // XVP - return this._handle_xvp_msg(); - - default: - this._fail("Unexpected server message", "Type:" + msg_type); - Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30)); - return true; - } - }, - - _onFlush: function() { - this._flushing = false; - // Resume processing - if (this._sock.rQlen() > 0) { - this._handle_message(); - } - }, - - _framebufferUpdate: function () { - var ret = true; - var now; - - if (this._FBU.rects === 0) { - if (this._sock.rQwait("FBU header", 3, 1)) { return false; } - this._sock.rQskip8(); // Padding - this._FBU.rects = this._sock.rQshift16(); - this._FBU.bytes = 0; - this._timing.cur_fbu = 0; - if (this._timing.fbu_rt_start > 0) { - now = (new Date()).getTime(); - Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start)); - } - - // Make sure the previous frame is fully rendered first - // to avoid building up an excessive queue - if (this._display.pending()) { - this._flushing = true; - this._display.flush(); - return false; - } - } - - while (this._FBU.rects > 0) { - if (this._rfb_connection_state !== 'connected') { return false; } - - if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; } - if (this._FBU.bytes === 0) { - if (this._sock.rQwait("rect header", 12)) { return false; } - /* New FramebufferUpdate */ - - var hdr = this._sock.rQshiftBytes(12); - this._FBU.x = (hdr[0] << 8) + hdr[1]; - this._FBU.y = (hdr[2] << 8) + hdr[3]; - this._FBU.width = (hdr[4] << 8) + hdr[5]; - this._FBU.height = (hdr[6] << 8) + hdr[7]; - this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + - (hdr[10] << 8) + hdr[11], 10); - - this._onFBUReceive(this, - {'x': this._FBU.x, 'y': this._FBU.y, - 'width': this._FBU.width, 'height': this._FBU.height, - 'encoding': this._FBU.encoding, - 'encodingName': this._encNames[this._FBU.encoding]}); - - if (!this._encNames[this._FBU.encoding]) { - this._fail("Unexpected server message", - "Unsupported encoding " + - this._FBU.encoding); - return false; - } - } - - this._timing.last_fbu = (new Date()).getTime(); - - ret = this._encHandlers[this._FBU.encoding](); - + this._FBU.rects = this._sock.rQshift16(); + this._FBU.bytes = 0; + this._timing.cur_fbu = 0; + if (this._timing.fbu_rt_start > 0) { now = (new Date()).getTime(); - this._timing.cur_fbu += (now - this._timing.last_fbu); - - if (ret) { - this._encStats[this._FBU.encoding][0]++; - this._encStats[this._FBU.encoding][1]++; - this._timing.pixels += this._FBU.width * this._FBU.height; - } - - if (this._timing.pixels >= (this._fb_width * this._fb_height)) { - if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) || - this._timing.fbu_rt_start > 0) { - this._timing.full_fbu_total += this._timing.cur_fbu; - this._timing.full_fbu_cnt++; - Util.Info("Timing of full FBU, curr: " + - this._timing.cur_fbu + ", total: " + - this._timing.full_fbu_total + ", cnt: " + - this._timing.full_fbu_cnt + ", avg: " + - (this._timing.full_fbu_total / this._timing.full_fbu_cnt)); - } - - if (this._timing.fbu_rt_start > 0) { - var fbu_rt_diff = now - this._timing.fbu_rt_start; - this._timing.fbu_rt_total += fbu_rt_diff; - this._timing.fbu_rt_cnt++; - Util.Info("full FBU round-trip, cur: " + - fbu_rt_diff + ", total: " + - this._timing.fbu_rt_total + ", cnt: " + - this._timing.fbu_rt_cnt + ", avg: " + - (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt)); - this._timing.fbu_rt_start = 0; - } - } - - if (!ret) { return ret; } // need more data + Log.Info("First FBU latency: " + (now - this._timing.fbu_rt_start)); } - this._display.flip(); + // Make sure the previous frame is fully rendered first + // to avoid building up an excessive queue + if (this._display.pending()) { + this._flushing = true; + this._display.flush(); + return false; + } + } - this._onFBUComplete(this, + while (this._FBU.rects > 0) { + if (this._rfb_connection_state !== 'connected') { return false; } + + if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; } + if (this._FBU.bytes === 0) { + if (this._sock.rQwait("rect header", 12)) { return false; } + /* New FramebufferUpdate */ + + var hdr = this._sock.rQshiftBytes(12); + this._FBU.x = (hdr[0] << 8) + hdr[1]; + this._FBU.y = (hdr[2] << 8) + hdr[3]; + this._FBU.width = (hdr[4] << 8) + hdr[5]; + this._FBU.height = (hdr[6] << 8) + hdr[7]; + this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + + (hdr[10] << 8) + hdr[11], 10); + + this._onFBUReceive(this, {'x': this._FBU.x, 'y': this._FBU.y, 'width': this._FBU.width, 'height': this._FBU.height, 'encoding': this._FBU.encoding, 'encodingName': this._encNames[this._FBU.encoding]}); - return true; // We finished this FBU - }, - - _updateContinuousUpdates: function() { - if (!this._enabledContinuousUpdates) { return; } - - RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0, - this._fb_width, this._fb_height); - } - }; - - Util.make_properties(RFB, [ - ['target', 'wo', 'dom'], // VNC display rendering Canvas object - ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input - ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption - ['true_color', 'rw', 'bool'], // Request true color pixel data - ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor - ['shared', 'rw', 'bool'], // Request shared mode - ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard - ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields - ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection - ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection - ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to - ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags - - // Callback functions - ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change - ['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI - ['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished - ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required - ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received - ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received - ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed - ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed - ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized - ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received - ['onXvpInit', 'rw', 'func'] // onXvpInit(version): XVP extensions active for this connection - ]); - - RFB.prototype.set_local_cursor = function (cursor) { - if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) { - this._local_cursor = false; - this._display.disableLocalCursor(); //Only show server-side cursor - } else { - if (this._display.get_cursor_uri()) { - this._local_cursor = true; - } else { - Util.Warn("Browser does not support local cursor"); - this._display.disableLocalCursor(); - } - } - - // Need to send an updated list of encodings if we are connected - if (this._rfb_connection_state === "connected") { - RFB.messages.clientEncodings(this._sock, this._encodings, cursor, - this._true_color); - } - }; - - RFB.prototype.set_view_only = function (view_only) { - this._view_only = view_only; - - if (this._rfb_connection_state === "connecting" || - this._rfb_connection_state === "connected") { - if (view_only) { - this._keyboard.ungrab(); - this._mouse.ungrab(); - } else { - this._keyboard.grab(); - this._mouse.grab(); - } - } - }; - - RFB.prototype.get_display = function () { return this._display; }; - RFB.prototype.get_keyboard = function () { return this._keyboard; }; - RFB.prototype.get_mouse = function () { return this._mouse; }; - - // Class Methods - RFB.messages = { - keyEvent: function (sock, keysym, down) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 4; // msg-type - buff[offset + 1] = down; - - buff[offset + 2] = 0; - buff[offset + 3] = 0; - - buff[offset + 4] = (keysym >> 24); - buff[offset + 5] = (keysym >> 16); - buff[offset + 6] = (keysym >> 8); - buff[offset + 7] = keysym; - - sock._sQlen += 8; - sock.flush(); - }, - - QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) { - function getRFBkeycode(xt_scancode) { - var upperByte = (keycode >> 8); - var lowerByte = (keycode & 0x00ff); - if (upperByte === 0xe0 && lowerByte < 0x7f) { - lowerByte = lowerByte | 0x80; - return lowerByte; - } - return xt_scancode; - } - - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 255; // msg-type - buff[offset + 1] = 0; // sub msg-type - - buff[offset + 2] = (down >> 8); - buff[offset + 3] = down; - - buff[offset + 4] = (keysym >> 24); - buff[offset + 5] = (keysym >> 16); - buff[offset + 6] = (keysym >> 8); - buff[offset + 7] = keysym; - - var RFBkeycode = getRFBkeycode(keycode); - - buff[offset + 8] = (RFBkeycode >> 24); - buff[offset + 9] = (RFBkeycode >> 16); - buff[offset + 10] = (RFBkeycode >> 8); - buff[offset + 11] = RFBkeycode; - - sock._sQlen += 12; - sock.flush(); - }, - - pointerEvent: function (sock, x, y, mask) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 5; // msg-type - - buff[offset + 1] = mask; - - buff[offset + 2] = x >> 8; - buff[offset + 3] = x; - - buff[offset + 4] = y >> 8; - buff[offset + 5] = y; - - sock._sQlen += 6; - sock.flush(); - }, - - // TODO(directxman12): make this unicode compatible? - clientCutText: function (sock, text) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 6; // msg-type - - buff[offset + 1] = 0; // padding - buff[offset + 2] = 0; // padding - buff[offset + 3] = 0; // padding - - var n = text.length; - - buff[offset + 4] = n >> 24; - buff[offset + 5] = n >> 16; - buff[offset + 6] = n >> 8; - buff[offset + 7] = n; - - for (var i = 0; i < n; i++) { - buff[offset + 8 + i] = text.charCodeAt(i); - } - - sock._sQlen += 8 + n; - sock.flush(); - }, - - setDesktopSize: function (sock, width, height, id, flags) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 251; // msg-type - buff[offset + 1] = 0; // padding - buff[offset + 2] = width >> 8; // width - buff[offset + 3] = width; - buff[offset + 4] = height >> 8; // height - buff[offset + 5] = height; - - buff[offset + 6] = 1; // number-of-screens - buff[offset + 7] = 0; // padding - - // screen array - buff[offset + 8] = id >> 24; // id - buff[offset + 9] = id >> 16; - buff[offset + 10] = id >> 8; - buff[offset + 11] = id; - buff[offset + 12] = 0; // x-position - buff[offset + 13] = 0; - buff[offset + 14] = 0; // y-position - buff[offset + 15] = 0; - buff[offset + 16] = width >> 8; // width - buff[offset + 17] = width; - buff[offset + 18] = height >> 8; // height - buff[offset + 19] = height; - buff[offset + 20] = flags >> 24; // flags - buff[offset + 21] = flags >> 16; - buff[offset + 22] = flags >> 8; - buff[offset + 23] = flags; - - sock._sQlen += 24; - sock.flush(); - }, - - clientFence: function (sock, flags, payload) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 248; // msg-type - - buff[offset + 1] = 0; // padding - buff[offset + 2] = 0; // padding - buff[offset + 3] = 0; // padding - - buff[offset + 4] = flags >> 24; // flags - buff[offset + 5] = flags >> 16; - buff[offset + 6] = flags >> 8; - buff[offset + 7] = flags; - - var n = payload.length; - - buff[offset + 8] = n; // length - - for (var i = 0; i < n; i++) { - buff[offset + 9 + i] = payload.charCodeAt(i); - } - - sock._sQlen += 9 + n; - sock.flush(); - }, - - enableContinuousUpdates: function (sock, enable, x, y, width, height) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 150; // msg-type - buff[offset + 1] = enable; // enable-flag - - buff[offset + 2] = x >> 8; // x - buff[offset + 3] = x; - buff[offset + 4] = y >> 8; // y - buff[offset + 5] = y; - buff[offset + 6] = width >> 8; // width - buff[offset + 7] = width; - buff[offset + 8] = height >> 8; // height - buff[offset + 9] = height; - - sock._sQlen += 10; - sock.flush(); - }, - - pixelFormat: function (sock, bpp, depth, true_color) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 0; // msg-type - - buff[offset + 1] = 0; // padding - buff[offset + 2] = 0; // padding - buff[offset + 3] = 0; // padding - - buff[offset + 4] = bpp * 8; // bits-per-pixel - buff[offset + 5] = depth * 8; // depth - buff[offset + 6] = 0; // little-endian - buff[offset + 7] = true_color ? 1 : 0; // true-color - - buff[offset + 8] = 0; // red-max - buff[offset + 9] = 255; // red-max - - buff[offset + 10] = 0; // green-max - buff[offset + 11] = 255; // green-max - - buff[offset + 12] = 0; // blue-max - buff[offset + 13] = 255; // blue-max - - buff[offset + 14] = 16; // red-shift - buff[offset + 15] = 8; // green-shift - buff[offset + 16] = 0; // blue-shift - - buff[offset + 17] = 0; // padding - buff[offset + 18] = 0; // padding - buff[offset + 19] = 0; // padding - - sock._sQlen += 20; - sock.flush(); - }, - - clientEncodings: function (sock, encodings, local_cursor, true_color) { - var buff = sock._sQ; - var offset = sock._sQlen; - - buff[offset] = 2; // msg-type - buff[offset + 1] = 0; // padding - - // offset + 2 and offset + 3 are encoding count - - var i, j = offset + 4, cnt = 0; - for (i = 0; i < encodings.length; i++) { - if (encodings[i][0] === "Cursor" && !local_cursor) { - Util.Debug("Skipping Cursor pseudo-encoding"); - } else if (encodings[i][0] === "TIGHT" && !true_color) { - // TODO: remove this when we have tight+non-true-color - Util.Warn("Skipping tight as it is only supported with true color"); - } else { - var enc = encodings[i][1]; - buff[j] = enc >> 24; - buff[j + 1] = enc >> 16; - buff[j + 2] = enc >> 8; - buff[j + 3] = enc; - - j += 4; - cnt++; - } - } - - buff[offset + 2] = cnt >> 8; - buff[offset + 3] = cnt; - - sock._sQlen += j - offset; - sock.flush(); - }, - - fbUpdateRequest: function (sock, incremental, x, y, w, h) { - var buff = sock._sQ; - var offset = sock._sQlen; - - if (typeof(x) === "undefined") { x = 0; } - if (typeof(y) === "undefined") { y = 0; } - - buff[offset] = 3; // msg-type - buff[offset + 1] = incremental ? 1 : 0; - - buff[offset + 2] = (x >> 8) & 0xFF; - buff[offset + 3] = x & 0xFF; - - buff[offset + 4] = (y >> 8) & 0xFF; - buff[offset + 5] = y & 0xFF; - - buff[offset + 6] = (w >> 8) & 0xFF; - buff[offset + 7] = w & 0xFF; - - buff[offset + 8] = (h >> 8) & 0xFF; - buff[offset + 9] = h & 0xFF; - - sock._sQlen += 10; - sock.flush(); - } - }; - - RFB.genDES = function (password, challenge) { - var passwd = []; - for (var i = 0; i < password.length; i++) { - passwd.push(password.charCodeAt(i)); - } - return (new DES(passwd)).encrypt(challenge); - }; - - RFB.encodingHandlers = { - RAW: function () { - if (this._FBU.lines === 0) { - this._FBU.lines = this._FBU.height; - } - - this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line - if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; } - var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines); - var curr_height = Math.min(this._FBU.lines, - Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp))); - this._display.blitImage(this._FBU.x, cur_y, this._FBU.width, - curr_height, this._sock.get_rQ(), - this._sock.get_rQi()); - this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp); - this._FBU.lines -= curr_height; - - if (this._FBU.lines > 0) { - this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line - } else { - this._FBU.rects--; - this._FBU.bytes = 0; - } - - return true; - }, - - COPYRECT: function () { - this._FBU.bytes = 4; - if (this._sock.rQwait("COPYRECT", 4)) { return false; } - this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(), - this._FBU.x, this._FBU.y, this._FBU.width, - this._FBU.height); - - this._FBU.rects--; - this._FBU.bytes = 0; - return true; - }, - - RRE: function () { - var color; - if (this._FBU.subrects === 0) { - this._FBU.bytes = 4 + this._fb_Bpp; - if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; } - this._FBU.subrects = this._sock.rQshift32(); - color = this._sock.rQshiftBytes(this._fb_Bpp); // Background - this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color); - } - - while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) { - color = this._sock.rQshiftBytes(this._fb_Bpp); - var x = this._sock.rQshift16(); - var y = this._sock.rQshift16(); - var width = this._sock.rQshift16(); - var height = this._sock.rQshift16(); - this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color); - this._FBU.subrects--; - } - - if (this._FBU.subrects > 0) { - var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects); - this._FBU.bytes = (this._fb_Bpp + 8) * chunk; - } else { - this._FBU.rects--; - this._FBU.bytes = 0; - } - - return true; - }, - - HEXTILE: function () { - var rQ = this._sock.get_rQ(); - var rQi = this._sock.get_rQi(); - - if (this._FBU.tiles === 0) { - this._FBU.tiles_x = Math.ceil(this._FBU.width / 16); - this._FBU.tiles_y = Math.ceil(this._FBU.height / 16); - this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y; - this._FBU.tiles = this._FBU.total_tiles; - } - - while (this._FBU.tiles > 0) { - this._FBU.bytes = 1; - if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; } - var subencoding = rQ[rQi]; // Peek - if (subencoding > 30) { // Raw + if (!this._encNames[this._FBU.encoding]) { this._fail("Unexpected server message", - "Illegal hextile subencoding: " + subencoding); + "Unsupported encoding " + + this._FBU.encoding); return false; } + } - var subrects = 0; - var curr_tile = this._FBU.total_tiles - this._FBU.tiles; - var tile_x = curr_tile % this._FBU.tiles_x; - var tile_y = Math.floor(curr_tile / this._FBU.tiles_x); - var x = this._FBU.x + tile_x * 16; - var y = this._FBU.y + tile_y * 16; - var w = Math.min(16, (this._FBU.x + this._FBU.width) - x); - var h = Math.min(16, (this._FBU.y + this._FBU.height) - y); + this._timing.last_fbu = (new Date()).getTime(); - // Figure out how much we are expecting - if (subencoding & 0x01) { // Raw - this._FBU.bytes += w * h * this._fb_Bpp; - } else { - if (subencoding & 0x02) { // Background - this._FBU.bytes += this._fb_Bpp; - } - if (subencoding & 0x04) { // Foreground - this._FBU.bytes += this._fb_Bpp; - } - if (subencoding & 0x08) { // AnySubrects - this._FBU.bytes++; // Since we aren't shifting it off - if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; } - subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek - if (subencoding & 0x10) { // SubrectsColoured - this._FBU.bytes += subrects * (this._fb_Bpp + 2); - } else { - this._FBU.bytes += subrects * 2; - } - } + ret = this._encHandlers[this._FBU.encoding](); + + now = (new Date()).getTime(); + this._timing.cur_fbu += (now - this._timing.last_fbu); + + if (ret) { + this._encStats[this._FBU.encoding][0]++; + this._encStats[this._FBU.encoding][1]++; + this._timing.pixels += this._FBU.width * this._FBU.height; + } + + if (this._timing.pixels >= (this._fb_width * this._fb_height)) { + if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) || + this._timing.fbu_rt_start > 0) { + this._timing.full_fbu_total += this._timing.cur_fbu; + this._timing.full_fbu_cnt++; + Log.Info("Timing of full FBU, curr: " + + this._timing.cur_fbu + ", total: " + + this._timing.full_fbu_total + ", cnt: " + + this._timing.full_fbu_cnt + ", avg: " + + (this._timing.full_fbu_total / this._timing.full_fbu_cnt)); } - if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; } + if (this._timing.fbu_rt_start > 0) { + var fbu_rt_diff = now - this._timing.fbu_rt_start; + this._timing.fbu_rt_total += fbu_rt_diff; + this._timing.fbu_rt_cnt++; + Log.Info("full FBU round-trip, cur: " + + fbu_rt_diff + ", total: " + + this._timing.fbu_rt_total + ", cnt: " + + this._timing.fbu_rt_cnt + ", avg: " + + (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt)); + this._timing.fbu_rt_start = 0; + } + } - // We know the encoding and have a whole tile - this._FBU.subencoding = rQ[rQi]; - rQi++; - if (this._FBU.subencoding === 0) { - if (this._FBU.lastsubencoding & 0x01) { - // Weird: ignore blanks are RAW - Util.Debug(" Ignoring blank after RAW"); + if (!ret) { return ret; } // need more data + } + + this._display.flip(); + + this._onFBUComplete(this, + {'x': this._FBU.x, 'y': this._FBU.y, + 'width': this._FBU.width, 'height': this._FBU.height, + 'encoding': this._FBU.encoding, + 'encodingName': this._encNames[this._FBU.encoding]}); + + return true; // We finished this FBU + }, + + _updateContinuousUpdates: function() { + if (!this._enabledContinuousUpdates) { return; } + + RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0, + this._fb_width, this._fb_height); + } +}; + +make_properties(RFB, [ + ['target', 'wo', 'dom'], // VNC display rendering Canvas object + ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input + ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption + ['true_color', 'rw', 'bool'], // Request true color pixel data + ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor + ['shared', 'rw', 'bool'], // Request shared mode + ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard + ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields + ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection + ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection + ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to + ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags + + // Callback functions + ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change + ['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI + ['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished + ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required + ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received + ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received + ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed + ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed + ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized + ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received + ['onXvpInit', 'rw', 'func'] // onXvpInit(version): XVP extensions active for this connection +]); + +RFB.prototype.set_local_cursor = function (cursor) { + if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) { + this._local_cursor = false; + this._display.disableLocalCursor(); //Only show server-side cursor + } else { + if (this._display.get_cursor_uri()) { + this._local_cursor = true; + } else { + Log.Warn("Browser does not support local cursor"); + this._display.disableLocalCursor(); + } + } + + // Need to send an updated list of encodings if we are connected + if (this._rfb_connection_state === "connected") { + RFB.messages.clientEncodings(this._sock, this._encodings, cursor, + this._true_color); + } +}; + +RFB.prototype.set_view_only = function (view_only) { + this._view_only = view_only; + + if (this._rfb_connection_state === "connecting" || + this._rfb_connection_state === "connected") { + if (view_only) { + this._keyboard.ungrab(); + this._mouse.ungrab(); + } else { + this._keyboard.grab(); + this._mouse.grab(); + } + } +}; + +RFB.prototype.get_display = function () { return this._display; }; +RFB.prototype.get_keyboard = function () { return this._keyboard; }; +RFB.prototype.get_mouse = function () { return this._mouse; }; + +// Class Methods +RFB.messages = { + keyEvent: function (sock, keysym, down) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 4; // msg-type + buff[offset + 1] = down; + + buff[offset + 2] = 0; + buff[offset + 3] = 0; + + buff[offset + 4] = (keysym >> 24); + buff[offset + 5] = (keysym >> 16); + buff[offset + 6] = (keysym >> 8); + buff[offset + 7] = keysym; + + sock._sQlen += 8; + sock.flush(); + }, + + QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) { + function getRFBkeycode(xt_scancode) { + var upperByte = (keycode >> 8); + var lowerByte = (keycode & 0x00ff); + if (upperByte === 0xe0 && lowerByte < 0x7f) { + lowerByte = lowerByte | 0x80; + return lowerByte; + } + return xt_scancode; + } + + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 255; // msg-type + buff[offset + 1] = 0; // sub msg-type + + buff[offset + 2] = (down >> 8); + buff[offset + 3] = down; + + buff[offset + 4] = (keysym >> 24); + buff[offset + 5] = (keysym >> 16); + buff[offset + 6] = (keysym >> 8); + buff[offset + 7] = keysym; + + var RFBkeycode = getRFBkeycode(keycode); + + buff[offset + 8] = (RFBkeycode >> 24); + buff[offset + 9] = (RFBkeycode >> 16); + buff[offset + 10] = (RFBkeycode >> 8); + buff[offset + 11] = RFBkeycode; + + sock._sQlen += 12; + sock.flush(); + }, + + pointerEvent: function (sock, x, y, mask) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 5; // msg-type + + buff[offset + 1] = mask; + + buff[offset + 2] = x >> 8; + buff[offset + 3] = x; + + buff[offset + 4] = y >> 8; + buff[offset + 5] = y; + + sock._sQlen += 6; + sock.flush(); + }, + + // TODO(directxman12): make this unicode compatible? + clientCutText: function (sock, text) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 6; // msg-type + + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding + + var n = text.length; + + buff[offset + 4] = n >> 24; + buff[offset + 5] = n >> 16; + buff[offset + 6] = n >> 8; + buff[offset + 7] = n; + + for (var i = 0; i < n; i++) { + buff[offset + 8 + i] = text.charCodeAt(i); + } + + sock._sQlen += 8 + n; + sock.flush(); + }, + + setDesktopSize: function (sock, width, height, id, flags) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 251; // msg-type + buff[offset + 1] = 0; // padding + buff[offset + 2] = width >> 8; // width + buff[offset + 3] = width; + buff[offset + 4] = height >> 8; // height + buff[offset + 5] = height; + + buff[offset + 6] = 1; // number-of-screens + buff[offset + 7] = 0; // padding + + // screen array + buff[offset + 8] = id >> 24; // id + buff[offset + 9] = id >> 16; + buff[offset + 10] = id >> 8; + buff[offset + 11] = id; + buff[offset + 12] = 0; // x-position + buff[offset + 13] = 0; + buff[offset + 14] = 0; // y-position + buff[offset + 15] = 0; + buff[offset + 16] = width >> 8; // width + buff[offset + 17] = width; + buff[offset + 18] = height >> 8; // height + buff[offset + 19] = height; + buff[offset + 20] = flags >> 24; // flags + buff[offset + 21] = flags >> 16; + buff[offset + 22] = flags >> 8; + buff[offset + 23] = flags; + + sock._sQlen += 24; + sock.flush(); + }, + + clientFence: function (sock, flags, payload) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 248; // msg-type + + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding + + buff[offset + 4] = flags >> 24; // flags + buff[offset + 5] = flags >> 16; + buff[offset + 6] = flags >> 8; + buff[offset + 7] = flags; + + var n = payload.length; + + buff[offset + 8] = n; // length + + for (var i = 0; i < n; i++) { + buff[offset + 9 + i] = payload.charCodeAt(i); + } + + sock._sQlen += 9 + n; + sock.flush(); + }, + + enableContinuousUpdates: function (sock, enable, x, y, width, height) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 150; // msg-type + buff[offset + 1] = enable; // enable-flag + + buff[offset + 2] = x >> 8; // x + buff[offset + 3] = x; + buff[offset + 4] = y >> 8; // y + buff[offset + 5] = y; + buff[offset + 6] = width >> 8; // width + buff[offset + 7] = width; + buff[offset + 8] = height >> 8; // height + buff[offset + 9] = height; + + sock._sQlen += 10; + sock.flush(); + }, + + pixelFormat: function (sock, bpp, depth, true_color) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 0; // msg-type + + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding + + buff[offset + 4] = bpp * 8; // bits-per-pixel + buff[offset + 5] = depth * 8; // depth + buff[offset + 6] = 0; // little-endian + buff[offset + 7] = true_color ? 1 : 0; // true-color + + buff[offset + 8] = 0; // red-max + buff[offset + 9] = 255; // red-max + + buff[offset + 10] = 0; // green-max + buff[offset + 11] = 255; // green-max + + buff[offset + 12] = 0; // blue-max + buff[offset + 13] = 255; // blue-max + + buff[offset + 14] = 16; // red-shift + buff[offset + 15] = 8; // green-shift + buff[offset + 16] = 0; // blue-shift + + buff[offset + 17] = 0; // padding + buff[offset + 18] = 0; // padding + buff[offset + 19] = 0; // padding + + sock._sQlen += 20; + sock.flush(); + }, + + clientEncodings: function (sock, encodings, local_cursor, true_color) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 2; // msg-type + buff[offset + 1] = 0; // padding + + // offset + 2 and offset + 3 are encoding count + + var i, j = offset + 4, cnt = 0; + for (i = 0; i < encodings.length; i++) { + if (encodings[i][0] === "Cursor" && !local_cursor) { + Log.Debug("Skipping Cursor pseudo-encoding"); + } else if (encodings[i][0] === "TIGHT" && !true_color) { + // TODO: remove this when we have tight+non-true-color + Log.Warn("Skipping tight as it is only supported with true color"); + } else { + var enc = encodings[i][1]; + buff[j] = enc >> 24; + buff[j + 1] = enc >> 16; + buff[j + 2] = enc >> 8; + buff[j + 3] = enc; + + j += 4; + cnt++; + } + } + + buff[offset + 2] = cnt >> 8; + buff[offset + 3] = cnt; + + sock._sQlen += j - offset; + sock.flush(); + }, + + fbUpdateRequest: function (sock, incremental, x, y, w, h) { + var buff = sock._sQ; + var offset = sock._sQlen; + + if (typeof(x) === "undefined") { x = 0; } + if (typeof(y) === "undefined") { y = 0; } + + buff[offset] = 3; // msg-type + buff[offset + 1] = incremental ? 1 : 0; + + buff[offset + 2] = (x >> 8) & 0xFF; + buff[offset + 3] = x & 0xFF; + + buff[offset + 4] = (y >> 8) & 0xFF; + buff[offset + 5] = y & 0xFF; + + buff[offset + 6] = (w >> 8) & 0xFF; + buff[offset + 7] = w & 0xFF; + + buff[offset + 8] = (h >> 8) & 0xFF; + buff[offset + 9] = h & 0xFF; + + sock._sQlen += 10; + sock.flush(); + } +}; + +RFB.genDES = function (password, challenge) { + var passwd = []; + for (var i = 0; i < password.length; i++) { + passwd.push(password.charCodeAt(i)); + } + return (new DES(passwd)).encrypt(challenge); +}; + +RFB.encodingHandlers = { + RAW: function () { + if (this._FBU.lines === 0) { + this._FBU.lines = this._FBU.height; + } + + this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line + if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; } + var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines); + var curr_height = Math.min(this._FBU.lines, + Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp))); + this._display.blitImage(this._FBU.x, cur_y, this._FBU.width, + curr_height, this._sock.get_rQ(), + this._sock.get_rQi()); + this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp); + this._FBU.lines -= curr_height; + + if (this._FBU.lines > 0) { + this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line + } else { + this._FBU.rects--; + this._FBU.bytes = 0; + } + + return true; + }, + + COPYRECT: function () { + this._FBU.bytes = 4; + if (this._sock.rQwait("COPYRECT", 4)) { return false; } + this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(), + this._FBU.x, this._FBU.y, this._FBU.width, + this._FBU.height); + + this._FBU.rects--; + this._FBU.bytes = 0; + return true; + }, + + RRE: function () { + var color; + if (this._FBU.subrects === 0) { + this._FBU.bytes = 4 + this._fb_Bpp; + if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; } + this._FBU.subrects = this._sock.rQshift32(); + color = this._sock.rQshiftBytes(this._fb_Bpp); // Background + this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color); + } + + while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) { + color = this._sock.rQshiftBytes(this._fb_Bpp); + var x = this._sock.rQshift16(); + var y = this._sock.rQshift16(); + var width = this._sock.rQshift16(); + var height = this._sock.rQshift16(); + this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color); + this._FBU.subrects--; + } + + if (this._FBU.subrects > 0) { + var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects); + this._FBU.bytes = (this._fb_Bpp + 8) * chunk; + } else { + this._FBU.rects--; + this._FBU.bytes = 0; + } + + return true; + }, + + HEXTILE: function () { + var rQ = this._sock.get_rQ(); + var rQi = this._sock.get_rQi(); + + if (this._FBU.tiles === 0) { + this._FBU.tiles_x = Math.ceil(this._FBU.width / 16); + this._FBU.tiles_y = Math.ceil(this._FBU.height / 16); + this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y; + this._FBU.tiles = this._FBU.total_tiles; + } + + while (this._FBU.tiles > 0) { + this._FBU.bytes = 1; + if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; } + var subencoding = rQ[rQi]; // Peek + if (subencoding > 30) { // Raw + this._fail("Unexpected server message", + "Illegal hextile subencoding: " + subencoding); + return false; + } + + var subrects = 0; + var curr_tile = this._FBU.total_tiles - this._FBU.tiles; + var tile_x = curr_tile % this._FBU.tiles_x; + var tile_y = Math.floor(curr_tile / this._FBU.tiles_x); + var x = this._FBU.x + tile_x * 16; + var y = this._FBU.y + tile_y * 16; + var w = Math.min(16, (this._FBU.x + this._FBU.width) - x); + var h = Math.min(16, (this._FBU.y + this._FBU.height) - y); + + // Figure out how much we are expecting + if (subencoding & 0x01) { // Raw + this._FBU.bytes += w * h * this._fb_Bpp; + } else { + if (subencoding & 0x02) { // Background + this._FBU.bytes += this._fb_Bpp; + } + if (subencoding & 0x04) { // Foreground + this._FBU.bytes += this._fb_Bpp; + } + if (subencoding & 0x08) { // AnySubrects + this._FBU.bytes++; // Since we aren't shifting it off + if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; } + subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek + if (subencoding & 0x10) { // SubrectsColoured + this._FBU.bytes += subrects * (this._fb_Bpp + 2); } else { - this._display.fillRect(x, y, w, h, this._FBU.background); + this._FBU.bytes += subrects * 2; } - } else if (this._FBU.subencoding & 0x01) { // Raw - this._display.blitImage(x, y, w, h, rQ, rQi); - rQi += this._FBU.bytes - 1; + } + } + + if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; } + + // We know the encoding and have a whole tile + this._FBU.subencoding = rQ[rQi]; + rQi++; + if (this._FBU.subencoding === 0) { + if (this._FBU.lastsubencoding & 0x01) { + // Weird: ignore blanks are RAW + Log.Debug(" Ignoring blank after RAW"); } else { - if (this._FBU.subencoding & 0x02) { // Background - if (this._fb_Bpp == 1) { - this._FBU.background = rQ[rQi]; - } else { - // fb_Bpp is 4 - this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; - } - rQi += this._fb_Bpp; + this._display.fillRect(x, y, w, h, this._FBU.background); + } + } else if (this._FBU.subencoding & 0x01) { // Raw + this._display.blitImage(x, y, w, h, rQ, rQi); + rQi += this._FBU.bytes - 1; + } else { + if (this._FBU.subencoding & 0x02) { // Background + if (this._fb_Bpp == 1) { + this._FBU.background = rQ[rQi]; + } else { + // fb_Bpp is 4 + this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; } - if (this._FBU.subencoding & 0x04) { // Foreground - if (this._fb_Bpp == 1) { - this._FBU.foreground = rQ[rQi]; - } else { - // this._fb_Bpp is 4 - this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; - } - rQi += this._fb_Bpp; + rQi += this._fb_Bpp; + } + if (this._FBU.subencoding & 0x04) { // Foreground + if (this._fb_Bpp == 1) { + this._FBU.foreground = rQ[rQi]; + } else { + // this._fb_Bpp is 4 + this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; } + rQi += this._fb_Bpp; + } - this._display.startTile(x, y, w, h, this._FBU.background); - if (this._FBU.subencoding & 0x08) { // AnySubrects - subrects = rQ[rQi]; - rQi++; + this._display.startTile(x, y, w, h, this._FBU.background); + if (this._FBU.subencoding & 0x08) { // AnySubrects + subrects = rQ[rQi]; + rQi++; - for (var s = 0; s < subrects; s++) { - var color; - if (this._FBU.subencoding & 0x10) { // SubrectsColoured - if (this._fb_Bpp === 1) { - color = rQ[rQi]; - } else { - // _fb_Bpp is 4 - color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; - } - rQi += this._fb_Bpp; + for (var s = 0; s < subrects; s++) { + var color; + if (this._FBU.subencoding & 0x10) { // SubrectsColoured + if (this._fb_Bpp === 1) { + color = rQ[rQi]; } else { - color = this._FBU.foreground; + // _fb_Bpp is 4 + color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; } - var xy = rQ[rQi]; - rQi++; - var sx = (xy >> 4); - var sy = (xy & 0x0f); - - var wh = rQ[rQi]; - rQi++; - var sw = (wh >> 4) + 1; - var sh = (wh & 0x0f) + 1; - - this._display.subTile(sx, sy, sw, sh, color); + rQi += this._fb_Bpp; + } else { + color = this._FBU.foreground; } + var xy = rQ[rQi]; + rQi++; + var sx = (xy >> 4); + var sy = (xy & 0x0f); + + var wh = rQ[rQi]; + rQi++; + var sw = (wh >> 4) + 1; + var sh = (wh & 0x0f) + 1; + + this._display.subTile(sx, sy, sw, sh, color); } - this._display.finishTile(); } - this._sock.set_rQi(rQi); - this._FBU.lastsubencoding = this._FBU.subencoding; - this._FBU.bytes = 0; - this._FBU.tiles--; + this._display.finishTile(); } + this._sock.set_rQi(rQi); + this._FBU.lastsubencoding = this._FBU.subencoding; + this._FBU.bytes = 0; + this._FBU.tiles--; + } - if (this._FBU.tiles === 0) { - this._FBU.rects--; - } + if (this._FBU.tiles === 0) { + this._FBU.rects--; + } - return true; - }, + return true; + }, - getTightCLength: function (arr) { - var header = 1, data = 0; - data += arr[0] & 0x7f; - if (arr[0] & 0x80) { + getTightCLength: function (arr) { + var header = 1, data = 0; + data += arr[0] & 0x7f; + if (arr[0] & 0x80) { + header++; + data += (arr[1] & 0x7f) << 7; + if (arr[1] & 0x80) { header++; - data += (arr[1] & 0x7f) << 7; - if (arr[1] & 0x80) { - header++; - data += arr[2] << 14; + data += arr[2] << 14; + } + } + return [header, data]; + }, + + display_tight: function (isTightPNG) { + if (this._fb_depth === 1) { + this._fail("Internal error", + "Tight protocol handler only implements " + + "true color mode"); + } + + this._FBU.bytes = 1; // compression-control byte + if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; } + + var checksum = function (data) { + var sum = 0; + for (var i = 0; i < data.length; i++) { + sum += data[i]; + if (sum > 65536) sum -= 65536; + } + return sum; + }; + + var resetStreams = 0; + var streamId = -1; + var decompress = function (data, expected) { + for (var i = 0; i < 4; i++) { + if ((resetStreams >> i) & 1) { + this._FBU.zlibs[i].reset(); + Log.Info("Reset zlib stream " + i); } } - return [header, data]; - }, - display_tight: function (isTightPNG) { - if (this._fb_depth === 1) { - this._fail("Internal error", - "Tight protocol handler only implements " + - "true color mode"); - } + //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); + var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected); + /*if (uncompressed.status !== 0) { + Log.Error("Invalid data in zlib stream"); + }*/ - this._FBU.bytes = 1; // compression-control byte - if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; } + //return uncompressed.data; + return uncompressed; + }.bind(this); - var checksum = function (data) { - var sum = 0; - for (var i = 0; i < data.length; i++) { - sum += data[i]; - if (sum > 65536) sum -= 65536; - } - return sum; - }; - - var resetStreams = 0; - var streamId = -1; - var decompress = function (data, expected) { - for (var i = 0; i < 4; i++) { - if ((resetStreams >> i) & 1) { - this._FBU.zlibs[i].reset(); - Util.Info("Reset zlib stream " + i); - } - } - - //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); - var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected); - /*if (uncompressed.status !== 0) { - Util.Error("Invalid data in zlib stream"); - }*/ - - //return uncompressed.data; - return uncompressed; - }.bind(this); - - var indexedToRGBX2Color = function (data, palette, width, height) { - // Convert indexed (palette based) image data to RGB - // TODO: reduce number of calculations inside loop - var dest = this._destBuff; - var w = Math.floor((width + 7) / 8); - var w1 = Math.floor(width / 8); - - /*for (var y = 0; y < height; y++) { - var b, x, dp, sp; - var yoffset = y * width; - var ybitoffset = y * w; - var xoffset, targetbyte; - for (x = 0; x < w1; x++) { - xoffset = yoffset + x * 8; - targetbyte = data[ybitoffset + x]; - for (b = 7; b >= 0; b--) { - dp = (xoffset + 7 - b) * 3; - sp = (targetbyte >> b & 1) * 3; - dest[dp] = palette[sp]; - dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp + 2]; - } - } + var indexedToRGBX2Color = function (data, palette, width, height) { + // Convert indexed (palette based) image data to RGB + // TODO: reduce number of calculations inside loop + var dest = this._destBuff; + var w = Math.floor((width + 7) / 8); + var w1 = Math.floor(width / 8); + /*for (var y = 0; y < height; y++) { + var b, x, dp, sp; + var yoffset = y * width; + var ybitoffset = y * w; + var xoffset, targetbyte; + for (x = 0; x < w1; x++) { xoffset = yoffset + x * 8; targetbyte = data[ybitoffset + x]; - for (b = 7; b >= 8 - width % 8; b--) { + for (b = 7; b >= 0; b--) { dp = (xoffset + 7 - b) * 3; sp = (targetbyte >> b & 1) * 3; dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; dest[dp + 2] = palette[sp + 2]; } - }*/ + } - for (var y = 0; y < height; y++) { - var b, x, dp, sp; - for (x = 0; x < w1; x++) { - for (b = 7; b >= 0; b--) { - dp = (y * width + x * 8 + 7 - b) * 4; - sp = (data[y * w + x] >> b & 1) * 3; - dest[dp] = palette[sp]; - dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp + 2]; - dest[dp + 3] = 255; - } - } + xoffset = yoffset + x * 8; + targetbyte = data[ybitoffset + x]; + for (b = 7; b >= 8 - width % 8; b--) { + dp = (xoffset + 7 - b) * 3; + sp = (targetbyte >> b & 1) * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; + } + }*/ - for (b = 7; b >= 8 - width % 8; b--) { + for (var y = 0; y < height; y++) { + var b, x, dp, sp; + for (x = 0; x < w1; x++) { + for (b = 7; b >= 0; b--) { dp = (y * width + x * 8 + 7 - b) * 4; sp = (data[y * w + x] >> b & 1) * 3; dest[dp] = palette[sp]; @@ -2120,356 +2108,365 @@ export default function RFB(defaults) { } } - return dest; - }.bind(this); - - var indexedToRGBX = function (data, palette, width, height) { - // Convert indexed (palette based) image data to RGB - var dest = this._destBuff; - var total = width * height * 4; - for (var i = 0, j = 0; i < total; i += 4, j++) { - var sp = data[j] * 3; - dest[i] = palette[sp]; - dest[i + 1] = palette[sp + 1]; - dest[i + 2] = palette[sp + 2]; - dest[i + 3] = 255; + for (b = 7; b >= 8 - width % 8; b--) { + dp = (y * width + x * 8 + 7 - b) * 4; + sp = (data[y * w + x] >> b & 1) * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; + dest[dp + 3] = 255; } - - return dest; - }.bind(this); - - var rQi = this._sock.get_rQi(); - var rQ = this._sock.rQwhole(); - var cmode, data; - var cl_header, cl_data; - - var handlePalette = function () { - var numColors = rQ[rQi + 2] + 1; - var paletteSize = numColors * this._fb_depth; - this._FBU.bytes += paletteSize; - if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; } - - var bpp = (numColors <= 2) ? 1 : 8; - var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8); - var raw = false; - if (rowSize * this._FBU.height < 12) { - raw = true; - cl_header = 0; - cl_data = rowSize * this._FBU.height; - //clength = [0, rowSize * this._FBU.height]; - } else { - // begin inline getTightCLength (returning two-item arrays is bad for performance with GC) - var cl_offset = rQi + 3 + paletteSize; - cl_header = 1; - cl_data = 0; - cl_data += rQ[cl_offset] & 0x7f; - if (rQ[cl_offset] & 0x80) { - cl_header++; - cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; - if (rQ[cl_offset + 1] & 0x80) { - cl_header++; - cl_data += rQ[cl_offset + 2] << 14; - } - } - // end inline getTightCLength - } - - this._FBU.bytes += cl_header + cl_data; - if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } - - // Shift ctl, filter id, num colors, palette entries, and clength off - this._sock.rQskipBytes(3); - //var palette = this._sock.rQshiftBytes(paletteSize); - this._sock.rQshiftTo(this._paletteBuff, paletteSize); - this._sock.rQskipBytes(cl_header); - - if (raw) { - data = this._sock.rQshiftBytes(cl_data); - } else { - data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height); - } - - // Convert indexed (palette based) image data to RGB - var rgbx; - if (numColors == 2) { - rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height); - this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); - } else { - rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height); - this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); - } - - - return true; - }.bind(this); - - var handleCopy = function () { - var raw = false; - var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth; - if (uncompressedSize < 12) { - raw = true; - cl_header = 0; - cl_data = uncompressedSize; - } else { - // begin inline getTightCLength (returning two-item arrays is for peformance with GC) - var cl_offset = rQi + 1; - cl_header = 1; - cl_data = 0; - cl_data += rQ[cl_offset] & 0x7f; - if (rQ[cl_offset] & 0x80) { - cl_header++; - cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; - if (rQ[cl_offset + 1] & 0x80) { - cl_header++; - cl_data += rQ[cl_offset + 2] << 14; - } - } - // end inline getTightCLength - } - this._FBU.bytes = 1 + cl_header + cl_data; - if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } - - // Shift ctl, clength off - this._sock.rQshiftBytes(1 + cl_header); - - if (raw) { - data = this._sock.rQshiftBytes(cl_data); - } else { - data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize); - } - - this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false); - - return true; - }.bind(this); - - var ctl = this._sock.rQpeek8(); - - // Keep tight reset bits - resetStreams = ctl & 0xF; - - // Figure out filter - ctl = ctl >> 4; - streamId = ctl & 0x3; - - if (ctl === 0x08) cmode = "fill"; - else if (ctl === 0x09) cmode = "jpeg"; - else if (ctl === 0x0A) cmode = "png"; - else if (ctl & 0x04) cmode = "filter"; - else if (ctl < 0x04) cmode = "copy"; - else return this._fail("Unexpected server message", - "Illegal tight compression received, " + - "ctl: " + ctl); - - if (isTightPNG && (cmode === "filter" || cmode === "copy")) { - return this._fail("Unexpected server message", - "filter/copy received in tightPNG mode"); } - switch (cmode) { - // fill use fb_depth because TPIXELs drop the padding byte - case "fill": // TPIXEL - this._FBU.bytes += this._fb_depth; - break; - case "jpeg": // max clength - this._FBU.bytes += 3; - break; - case "png": // max clength - this._FBU.bytes += 3; - break; - case "filter": // filter id + num colors if palette - this._FBU.bytes += 2; - break; - case "copy": - break; + return dest; + }.bind(this); + + var indexedToRGBX = function (data, palette, width, height) { + // Convert indexed (palette based) image data to RGB + var dest = this._destBuff; + var total = width * height * 4; + for (var i = 0, j = 0; i < total; i += 4, j++) { + var sp = data[j] * 3; + dest[i] = palette[sp]; + dest[i + 1] = palette[sp + 1]; + dest[i + 2] = palette[sp + 2]; + dest[i + 3] = 255; } + return dest; + }.bind(this); + + var rQi = this._sock.get_rQi(); + var rQ = this._sock.rQwhole(); + var cmode, data; + var cl_header, cl_data; + + var handlePalette = function () { + var numColors = rQ[rQi + 2] + 1; + var paletteSize = numColors * this._fb_depth; + this._FBU.bytes += paletteSize; + if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; } + + var bpp = (numColors <= 2) ? 1 : 8; + var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8); + var raw = false; + if (rowSize * this._FBU.height < 12) { + raw = true; + cl_header = 0; + cl_data = rowSize * this._FBU.height; + //clength = [0, rowSize * this._FBU.height]; + } else { + // begin inline getTightCLength (returning two-item arrays is bad for performance with GC) + var cl_offset = rQi + 3 + paletteSize; + cl_header = 1; + cl_data = 0; + cl_data += rQ[cl_offset] & 0x7f; + if (rQ[cl_offset] & 0x80) { + cl_header++; + cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; + if (rQ[cl_offset + 1] & 0x80) { + cl_header++; + cl_data += rQ[cl_offset + 2] << 14; + } + } + // end inline getTightCLength + } + + this._FBU.bytes += cl_header + cl_data; if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } - // Determine FBU.bytes - switch (cmode) { - case "fill": - // skip ctl byte - this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false); - this._sock.rQskipBytes(4); - break; - case "png": - case "jpeg": - // begin inline getTightCLength (returning two-item arrays is for peformance with GC) - var cl_offset = rQi + 1; - cl_header = 1; - cl_data = 0; - cl_data += rQ[cl_offset] & 0x7f; - if (rQ[cl_offset] & 0x80) { + // Shift ctl, filter id, num colors, palette entries, and clength off + this._sock.rQskipBytes(3); + //var palette = this._sock.rQshiftBytes(paletteSize); + this._sock.rQshiftTo(this._paletteBuff, paletteSize); + this._sock.rQskipBytes(cl_header); + + if (raw) { + data = this._sock.rQshiftBytes(cl_data); + } else { + data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height); + } + + // Convert indexed (palette based) image data to RGB + var rgbx; + if (numColors == 2) { + rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height); + this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); + } else { + rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height); + this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); + } + + + return true; + }.bind(this); + + var handleCopy = function () { + var raw = false; + var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth; + if (uncompressedSize < 12) { + raw = true; + cl_header = 0; + cl_data = uncompressedSize; + } else { + // begin inline getTightCLength (returning two-item arrays is for peformance with GC) + var cl_offset = rQi + 1; + cl_header = 1; + cl_data = 0; + cl_data += rQ[cl_offset] & 0x7f; + if (rQ[cl_offset] & 0x80) { + cl_header++; + cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; + if (rQ[cl_offset + 1] & 0x80) { cl_header++; - cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; - if (rQ[cl_offset + 1] & 0x80) { - cl_header++; - cl_data += rQ[cl_offset + 2] << 14; - } + cl_data += rQ[cl_offset + 2] << 14; } - // end inline getTightCLength - this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data - if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } - - // We have everything, render it - this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length - data = this._sock.rQshiftBytes(cl_data); - this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data); - break; - case "filter": - var filterId = rQ[rQi + 1]; - if (filterId === 1) { - if (!handlePalette()) { return false; } - } else { - // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter - // Filter 2, Gradient is valid but not use if jpeg is enabled - this._fail("Unexpected server message", - "Unsupported tight subencoding received, " + - "filter: " + filterId); - } - break; - case "copy": - if (!handleCopy()) { return false; } - break; - } - - - this._FBU.bytes = 0; - this._FBU.rects--; - - return true; - }, - - TIGHT: function () { return this._encHandlers.display_tight(false); }, - TIGHT_PNG: function () { return this._encHandlers.display_tight(true); }, - - last_rect: function () { - this._FBU.rects = 0; - return true; - }, - - handle_FB_resize: function () { - this._fb_width = this._FBU.width; - this._fb_height = this._FBU.height; - this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); - this._display.resize(this._fb_width, this._fb_height); - this._onFBResize(this, this._fb_width, this._fb_height); - this._timing.fbu_rt_start = (new Date()).getTime(); - this._updateContinuousUpdates(); - - this._FBU.bytes = 0; - this._FBU.rects -= 1; - return true; - }, - - ExtendedDesktopSize: function () { - this._FBU.bytes = 1; - if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; } - - this._supportsSetDesktopSize = true; - var number_of_screens = this._sock.rQpeek8(); - - this._FBU.bytes = 4 + (number_of_screens * 16); - if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; } - - this._sock.rQskipBytes(1); // number-of-screens - this._sock.rQskipBytes(3); // padding - - for (var i = 0; i < number_of_screens; i += 1) { - // Save the id and flags of the first screen - if (i === 0) { - this._screen_id = this._sock.rQshiftBytes(4); // id - this._sock.rQskipBytes(2); // x-position - this._sock.rQskipBytes(2); // y-position - this._sock.rQskipBytes(2); // width - this._sock.rQskipBytes(2); // height - this._screen_flags = this._sock.rQshiftBytes(4); // flags - } else { - this._sock.rQskipBytes(16); } + // end inline getTightCLength + } + this._FBU.bytes = 1 + cl_header + cl_data; + if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } + + // Shift ctl, clength off + this._sock.rQshiftBytes(1 + cl_header); + + if (raw) { + data = this._sock.rQshiftBytes(cl_data); + } else { + data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize); } - /* - * The x-position indicates the reason for the change: - * - * 0 - server resized on its own - * 1 - this client requested the resize - * 2 - another client requested the resize - */ + this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false); - // We need to handle errors when we requested the resize. - if (this._FBU.x === 1 && this._FBU.y !== 0) { - var msg = ""; - // The y-position indicates the status code from the server - switch (this._FBU.y) { - case 1: - msg = "Resize is administratively prohibited"; - break; - case 2: - msg = "Out of resources"; - break; - case 3: - msg = "Invalid screen layout"; - break; - default: - msg = "Unknown reason"; - break; - } - this._notification("Server did not accept the resize request: " - + msg, 'normal'); - return true; - } - - this._encHandlers.handle_FB_resize(); return true; - }, + }.bind(this); - DesktopSize: function () { - this._encHandlers.handle_FB_resize(); - return true; - }, + var ctl = this._sock.rQpeek8(); - Cursor: function () { - Util.Debug(">> set_cursor"); - var x = this._FBU.x; // hotspot-x - var y = this._FBU.y; // hotspot-y - var w = this._FBU.width; - var h = this._FBU.height; + // Keep tight reset bits + resetStreams = ctl & 0xF; - var pixelslength = w * h * this._fb_Bpp; - var masklength = Math.floor((w + 7) / 8) * h; + // Figure out filter + ctl = ctl >> 4; + streamId = ctl & 0x3; - this._FBU.bytes = pixelslength + masklength; - if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; } + if (ctl === 0x08) cmode = "fill"; + else if (ctl === 0x09) cmode = "jpeg"; + else if (ctl === 0x0A) cmode = "png"; + else if (ctl & 0x04) cmode = "filter"; + else if (ctl < 0x04) cmode = "copy"; + else return this._fail("Unexpected server message", + "Illegal tight compression received, " + + "ctl: " + ctl); - this._display.changeCursor(this._sock.rQshiftBytes(pixelslength), - this._sock.rQshiftBytes(masklength), - x, y, w, h); - - this._FBU.bytes = 0; - this._FBU.rects--; - - Util.Debug("<< set_cursor"); - return true; - }, - - QEMUExtendedKeyEvent: function () { - this._FBU.rects--; - - var keyboardEvent = document.createEvent("keyboardEvent"); - if (keyboardEvent.code !== undefined) { - this._qemuExtKeyEventSupported = true; - this._keyboard.setQEMUVNCKeyboardHandler(); - } - }, - - JPEG_quality_lo: function () { - Util.Error("Server sent jpeg_quality pseudo-encoding"); - }, - - compress_lo: function () { - Util.Error("Server sent compress level pseudo-encoding"); + if (isTightPNG && (cmode === "filter" || cmode === "copy")) { + return this._fail("Unexpected server message", + "filter/copy received in tightPNG mode"); } - }; -})(); + + switch (cmode) { + // fill use fb_depth because TPIXELs drop the padding byte + case "fill": // TPIXEL + this._FBU.bytes += this._fb_depth; + break; + case "jpeg": // max clength + this._FBU.bytes += 3; + break; + case "png": // max clength + this._FBU.bytes += 3; + break; + case "filter": // filter id + num colors if palette + this._FBU.bytes += 2; + break; + case "copy": + break; + } + + if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } + + // Determine FBU.bytes + switch (cmode) { + case "fill": + // skip ctl byte + this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false); + this._sock.rQskipBytes(4); + break; + case "png": + case "jpeg": + // begin inline getTightCLength (returning two-item arrays is for peformance with GC) + var cl_offset = rQi + 1; + cl_header = 1; + cl_data = 0; + cl_data += rQ[cl_offset] & 0x7f; + if (rQ[cl_offset] & 0x80) { + cl_header++; + cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; + if (rQ[cl_offset + 1] & 0x80) { + cl_header++; + cl_data += rQ[cl_offset + 2] << 14; + } + } + // end inline getTightCLength + this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data + if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } + + // We have everything, render it + this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length + data = this._sock.rQshiftBytes(cl_data); + this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data); + break; + case "filter": + var filterId = rQ[rQi + 1]; + if (filterId === 1) { + if (!handlePalette()) { return false; } + } else { + // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter + // Filter 2, Gradient is valid but not use if jpeg is enabled + this._fail("Unexpected server message", + "Unsupported tight subencoding received, " + + "filter: " + filterId); + } + break; + case "copy": + if (!handleCopy()) { return false; } + break; + } + + + this._FBU.bytes = 0; + this._FBU.rects--; + + return true; + }, + + TIGHT: function () { return this._encHandlers.display_tight(false); }, + TIGHT_PNG: function () { return this._encHandlers.display_tight(true); }, + + last_rect: function () { + this._FBU.rects = 0; + return true; + }, + + handle_FB_resize: function () { + this._fb_width = this._FBU.width; + this._fb_height = this._FBU.height; + this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); + this._display.resize(this._fb_width, this._fb_height); + this._onFBResize(this, this._fb_width, this._fb_height); + this._timing.fbu_rt_start = (new Date()).getTime(); + this._updateContinuousUpdates(); + + this._FBU.bytes = 0; + this._FBU.rects -= 1; + return true; + }, + + ExtendedDesktopSize: function () { + this._FBU.bytes = 1; + if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; } + + this._supportsSetDesktopSize = true; + var number_of_screens = this._sock.rQpeek8(); + + this._FBU.bytes = 4 + (number_of_screens * 16); + if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; } + + this._sock.rQskipBytes(1); // number-of-screens + this._sock.rQskipBytes(3); // padding + + for (var i = 0; i < number_of_screens; i += 1) { + // Save the id and flags of the first screen + if (i === 0) { + this._screen_id = this._sock.rQshiftBytes(4); // id + this._sock.rQskipBytes(2); // x-position + this._sock.rQskipBytes(2); // y-position + this._sock.rQskipBytes(2); // width + this._sock.rQskipBytes(2); // height + this._screen_flags = this._sock.rQshiftBytes(4); // flags + } else { + this._sock.rQskipBytes(16); + } + } + + /* + * The x-position indicates the reason for the change: + * + * 0 - server resized on its own + * 1 - this client requested the resize + * 2 - another client requested the resize + */ + + // We need to handle errors when we requested the resize. + if (this._FBU.x === 1 && this._FBU.y !== 0) { + var msg = ""; + // The y-position indicates the status code from the server + switch (this._FBU.y) { + case 1: + msg = "Resize is administratively prohibited"; + break; + case 2: + msg = "Out of resources"; + break; + case 3: + msg = "Invalid screen layout"; + break; + default: + msg = "Unknown reason"; + break; + } + this._notification("Server did not accept the resize request: " + + msg, 'normal'); + return true; + } + + this._encHandlers.handle_FB_resize(); + return true; + }, + + DesktopSize: function () { + this._encHandlers.handle_FB_resize(); + return true; + }, + + Cursor: function () { + Log.Debug(">> set_cursor"); + var x = this._FBU.x; // hotspot-x + var y = this._FBU.y; // hotspot-y + var w = this._FBU.width; + var h = this._FBU.height; + + var pixelslength = w * h * this._fb_Bpp; + var masklength = Math.floor((w + 7) / 8) * h; + + this._FBU.bytes = pixelslength + masklength; + if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; } + + this._display.changeCursor(this._sock.rQshiftBytes(pixelslength), + this._sock.rQshiftBytes(masklength), + x, y, w, h); + + this._FBU.bytes = 0; + this._FBU.rects--; + + Log.Debug("<< set_cursor"); + return true; + }, + + QEMUExtendedKeyEvent: function () { + this._FBU.rects--; + + var keyboardEvent = document.createEvent("keyboardEvent"); + if (keyboardEvent.code !== undefined) { + this._qemuExtKeyEventSupported = true; + this._keyboard.setQEMUVNCKeyboardHandler(); + } + }, + + JPEG_quality_lo: function () { + Log.Error("Server sent jpeg_quality pseudo-encoding"); + }, + + compress_lo: function () { + Log.Error("Server sent compress level pseudo-encoding"); + } +}; diff --git a/core/util.js b/core/util.js deleted file mode 100644 index 08d7d78e..00000000 --- a/core/util.js +++ /dev/null @@ -1,624 +0,0 @@ -/* - * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Licensed under MPL 2.0 (see LICENSE.txt) - * - * See README.md for usage and integration instructions. - */ - -/* jshint white: false, nonstandard: true */ -/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */ - -var Util = {}; - -/* - * ------------------------------------------------------ - * Namespaced in Util - * ------------------------------------------------------ - */ - -/* - * Logging/debug routines - */ - -Util._log_level = 'warn'; -Util.init_logging = function (level) { - "use strict"; - if (typeof level === 'undefined') { - level = Util._log_level; - } else { - Util._log_level = level; - } - - Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; - if (typeof window.console !== "undefined") { - /* jshint -W086 */ - switch (level) { - case 'debug': - Util.Debug = console.debug.bind(window.console); - case 'info': - Util.Info = console.info.bind(window.console); - case 'warn': - Util.Warn = console.warn.bind(window.console); - case 'error': - Util.Error = console.error.bind(window.console); - case 'none': - break; - default: - throw new Error("invalid logging type '" + level + "'"); - } - /* jshint +W086 */ - } -}; -Util.get_logging = function () { - return Util._log_level; -}; -// Initialize logging level -Util.init_logging(); - -Util.make_property = function (proto, name, mode, type) { - "use strict"; - - var getter; - if (type === 'arr') { - getter = function (idx) { - if (typeof idx !== 'undefined') { - return this['_' + name][idx]; - } else { - return this['_' + name]; - } - }; - } else { - getter = function () { - return this['_' + name]; - }; - } - - var make_setter = function (process_val) { - if (process_val) { - return function (val, idx) { - if (typeof idx !== 'undefined') { - this['_' + name][idx] = process_val(val); - } else { - this['_' + name] = process_val(val); - } - }; - } else { - return function (val, idx) { - if (typeof idx !== 'undefined') { - this['_' + name][idx] = val; - } else { - this['_' + name] = val; - } - }; - } - }; - - var setter; - if (type === 'bool') { - setter = make_setter(function (val) { - if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) { - return false; - } else { - return true; - } - }); - } else if (type === 'int') { - setter = make_setter(function (val) { return parseInt(val, 10); }); - } else if (type === 'float') { - setter = make_setter(parseFloat); - } else if (type === 'str') { - setter = make_setter(String); - } else if (type === 'func') { - setter = make_setter(function (val) { - if (!val) { - return function () {}; - } else { - return val; - } - }); - } else if (type === 'arr' || type === 'dom' || type == 'raw') { - setter = make_setter(); - } else { - throw new Error('Unknown property type ' + type); // some sanity checking - } - - // set the getter - if (typeof proto['get_' + name] === 'undefined') { - proto['get_' + name] = getter; - } - - // set the setter if needed - if (typeof proto['set_' + name] === 'undefined') { - if (mode === 'rw') { - proto['set_' + name] = setter; - } else if (mode === 'wo') { - proto['set_' + name] = function (val, idx) { - if (typeof this['_' + name] !== 'undefined') { - throw new Error(name + " can only be set once"); - } - setter.call(this, val, idx); - }; - } - } - - // make a special setter that we can use in set defaults - proto['_raw_set_' + name] = function (val, idx) { - setter.call(this, val, idx); - //delete this['_init_set_' + name]; // remove it after use - }; -}; - -Util.make_properties = function (constructor, arr) { - "use strict"; - for (var i = 0; i < arr.length; i++) { - Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]); - } -}; - -Util.set_defaults = function (obj, conf, defaults) { - var defaults_keys = Object.keys(defaults); - var conf_keys = Object.keys(conf); - var keys_obj = {}; - var i; - for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; } - for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; } - var keys = Object.keys(keys_obj); - - for (i = 0; i < keys.length; i++) { - var setter = obj['_raw_set_' + keys[i]]; - if (!setter) { - Util.Warn('Invalid property ' + keys[i]); - continue; - } - - if (keys[i] in conf) { - setter.call(obj, conf[keys[i]]); - } else { - setter.call(obj, defaults[keys[i]]); - } - } -}; - -/* - * Decode from UTF-8 - */ -Util.decodeUTF8 = function (utf8string) { - "use strict"; - return decodeURIComponent(escape(utf8string)); -}; - - - -/* - * Cross-browser routines - */ - -Util.getPointerEvent = function (e) { - return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e; -}; - -Util.stopEvent = function (e) { - e.stopPropagation(); - e.preventDefault(); -}; - -// Touch detection -Util.isTouchDevice = ('ontouchstart' in document.documentElement) || - // requried for Chrome debugger - (document.ontouchstart !== undefined) || - // required for MS Surface - (navigator.maxTouchPoints > 0) || - (navigator.msMaxTouchPoints > 0); -window.addEventListener('touchstart', function onFirstTouch() { - Util.isTouchDevice = true; - window.removeEventListener('touchstart', onFirstTouch, false); -}, false); - -Util._cursor_uris_supported = null; - -Util.browserSupportsCursorURIs = function () { - if (Util._cursor_uris_supported === null) { - try { - var target = document.createElement('canvas'); - target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default'; - - if (target.style.cursor) { - Util.Info("Data URI scheme cursor supported"); - Util._cursor_uris_supported = true; - } else { - Util.Warn("Data URI scheme cursor not supported"); - Util._cursor_uris_supported = false; - } - } catch (exc) { - Util.Error("Data URI scheme cursor test exception: " + exc); - Util._cursor_uris_supported = false; - } - } - - return Util._cursor_uris_supported; -}; - -// Set browser engine versions. Based on mootools. -Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}; - -(function () { - "use strict"; - // 'presto': (function () { return (!window.opera) ? false : true; }()), - var detectPresto = function () { - return !!window.opera; - }; - - // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); - var detectTrident = function () { - if (!window.ActiveXObject) { - return false; - } else { - if (window.XMLHttpRequest) { - return (document.querySelectorAll) ? 6 : 5; - } else { - return 4; - } - } - }; - - // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()), - var detectInitialWebkit = function () { - try { - if (navigator.taintEnabled) { - return false; - } else { - if (Util.Features.xpath) { - return (Util.Features.query) ? 525 : 420; - } else { - return 419; - } - } - } catch (e) { - return false; - } - }; - - var detectActualWebkit = function (initial_ver) { - var re = /WebKit\/([0-9\.]*) /; - var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1]; - return parseFloat(str_ver, 10); - }; - - // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }()) - var detectGecko = function () { - /* jshint -W041 */ - if (!document.getBoxObjectFor && window.mozInnerScreenX == null) { - return false; - } else { - return (document.getElementsByClassName) ? 19 : 18; - } - /* jshint +W041 */ - }; - - Util.Engine = { - // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference) - //'presto': (function() { - // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), - 'presto': detectPresto(), - 'trident': detectTrident(), - 'webkit': detectInitialWebkit(), - 'gecko': detectGecko() - }; - - if (Util.Engine.webkit) { - // Extract actual webkit version if available - Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit); - } -})(); - -Util.Flash = (function () { - "use strict"; - var v, version; - try { - v = navigator.plugins['Shockwave Flash'].description; - } catch (err1) { - try { - v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); - } catch (err2) { - v = '0 r0'; - } - } - version = v.match(/\d+/g); - return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0}; -}()); - - -Util.Localisation = { - // Currently configured language - language: 'en', - - // Current dictionary of translations - dictionary: undefined, - - // Configure suitable language based on user preferences - setup: function (supportedLanguages) { - var userLanguages; - - Util.Localisation.language = 'en'; // Default: US English - - /* - * Navigator.languages only available in Chrome (32+) and FireFox (32+) - * Fall back to navigator.language for other browsers - */ - if (typeof window.navigator.languages == 'object') { - userLanguages = window.navigator.languages; - } else { - userLanguages = [navigator.language || navigator.userLanguage]; - } - - for (var i = 0;i < userLanguages.length;i++) { - var userLang = userLanguages[i]; - userLang = userLang.toLowerCase(); - userLang = userLang.replace("_", "-"); - userLang = userLang.split("-"); - - // Built-in default? - if ((userLang[0] === 'en') && - ((userLang[1] === undefined) || (userLang[1] === 'us'))) { - return; - } - - // First pass: perfect match - for (var j = 0;j < supportedLanguages.length;j++) { - var supLang = supportedLanguages[j]; - supLang = supLang.toLowerCase(); - supLang = supLang.replace("_", "-"); - supLang = supLang.split("-"); - - if (userLang[0] !== supLang[0]) - continue; - if (userLang[1] !== supLang[1]) - continue; - - Util.Localisation.language = supportedLanguages[j]; - return; - } - - // Second pass: fallback - for (var j = 0;j < supportedLanguages.length;j++) { - supLang = supportedLanguages[j]; - supLang = supLang.toLowerCase(); - supLang = supLang.replace("_", "-"); - supLang = supLang.split("-"); - - if (userLang[0] !== supLang[0]) - continue; - if (supLang[1] !== undefined) - continue; - - Util.Localisation.language = supportedLanguages[j]; - return; - } - } - }, - - // Retrieve localised text - get: function (id) { - if (typeof Util.Localisation.dictionary !== 'undefined' && Util.Localisation.dictionary[id]) { - return Util.Localisation.dictionary[id]; - } else { - return id; - } - }, - - // Traverses the DOM and translates relevant fields - // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate - translateDOM: function () { - function process(elem, enabled) { - function isAnyOf(searchElement, items) { - return items.indexOf(searchElement) !== -1; - } - - function translateAttribute(elem, attr) { - var str = elem.getAttribute(attr); - str = Util.Localisation.get(str); - elem.setAttribute(attr, str); - } - - function translateTextNode(node) { - var str = node.data.trim(); - str = Util.Localisation.get(str); - node.data = str; - } - - if (elem.hasAttribute("translate")) { - if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) { - enabled = true; - } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) { - enabled = false; - } - } - - if (enabled) { - if (elem.hasAttribute("abbr") && - elem.tagName === "TH") { - translateAttribute(elem, "abbr"); - } - if (elem.hasAttribute("alt") && - isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) { - translateAttribute(elem, "alt"); - } - if (elem.hasAttribute("download") && - isAnyOf(elem.tagName, ["A", "AREA"])) { - translateAttribute(elem, "download"); - } - if (elem.hasAttribute("label") && - isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP", - "OPTION", "TRACK"])) { - translateAttribute(elem, "label"); - } - // FIXME: Should update "lang" - if (elem.hasAttribute("placeholder") && - isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) { - translateAttribute(elem, "placeholder"); - } - if (elem.hasAttribute("title")) { - translateAttribute(elem, "title"); - } - if (elem.hasAttribute("value") && - elem.tagName === "INPUT" && - isAnyOf(elem.getAttribute("type"), ["reset", "button"])) { - translateAttribute(elem, "value"); - } - } - - for (var i = 0;i < elem.childNodes.length;i++) { - let node = elem.childNodes[i]; - if (node.nodeType === node.ELEMENT_NODE) { - process(node, enabled); - } else if (node.nodeType === node.TEXT_NODE && enabled) { - translateTextNode(node); - } - } - } - - process(document.body, true); - }, -}; - -// Emulate Element.setCapture() when not supported - -Util._captureRecursion = false; -Util._captureProxy = function (e) { - // Recursion protection as we'll see our own event - if (Util._captureRecursion) return; - - // Clone the event as we cannot dispatch an already dispatched event - var newEv = new e.constructor(e.type, e); - - Util._captureRecursion = true; - Util._captureElem.dispatchEvent(newEv); - Util._captureRecursion = false; - - // Avoid double events - e.stopPropagation(); - - // Respect the wishes of the redirected event handlers - if (newEv.defaultPrevented) { - e.preventDefault(); - } - - // Implicitly release the capture on button release - if ((e.type === "mouseup") || (e.type === "touchend")) { - Util.releaseCapture(); - } -}; - -// Follow cursor style of target element -Util._captureElemChanged = function() { - var captureElem = document.getElementById("noVNC_mouse_capture_elem"); - captureElem.style.cursor = window.getComputedStyle(Util._captureElem).cursor; -}; -Util._captureObserver = new MutationObserver(Util._captureElemChanged); - -Util._captureIndex = 0; - -Util.setCapture = function (elem) { - if (elem.setCapture) { - - elem.setCapture(); - - // IE releases capture on 'click' events which might not trigger - elem.addEventListener('mouseup', Util.releaseCapture); - elem.addEventListener('touchend', Util.releaseCapture); - - } else { - // Release any existing capture in case this method is - // called multiple times without coordination - Util.releaseCapture(); - - // Safari on iOS 9 has a broken constructor for TouchEvent. - // We are fine in this case however, since Safari seems to - // have some sort of implicit setCapture magic anyway. - if (window.TouchEvent !== undefined) { - try { - new TouchEvent("touchstart"); - } catch (TypeError) { - return; - } - } - - var captureElem = document.getElementById("noVNC_mouse_capture_elem"); - - if (captureElem === null) { - captureElem = document.createElement("div"); - captureElem.id = "noVNC_mouse_capture_elem"; - captureElem.style.position = "fixed"; - captureElem.style.top = "0px"; - captureElem.style.left = "0px"; - captureElem.style.width = "100%"; - captureElem.style.height = "100%"; - captureElem.style.zIndex = 10000; - captureElem.style.display = "none"; - document.body.appendChild(captureElem); - - // This is to make sure callers don't get confused by having - // our blocking element as the target - captureElem.addEventListener('contextmenu', Util._captureProxy); - - captureElem.addEventListener('mousemove', Util._captureProxy); - captureElem.addEventListener('mouseup', Util._captureProxy); - - captureElem.addEventListener('touchmove', Util._captureProxy); - captureElem.addEventListener('touchend', Util._captureProxy); - } - - Util._captureElem = elem; - Util._captureIndex++; - - // Track cursor and get initial cursor - Util._captureObserver.observe(elem, {attributes:true}); - Util._captureElemChanged(); - - captureElem.style.display = null; - - // We listen to events on window in order to keep tracking if it - // happens to leave the viewport - window.addEventListener('mousemove', Util._captureProxy); - window.addEventListener('mouseup', Util._captureProxy); - - window.addEventListener('touchmove', Util._captureProxy); - window.addEventListener('touchend', Util._captureProxy); - } -}; - -Util.releaseCapture = function () { - if (document.releaseCapture) { - - document.releaseCapture(); - - } else { - if (!Util._captureElem) { - return; - } - - // There might be events already queued, so we need to wait for - // them to flush. E.g. contextmenu in Microsoft Edge - window.setTimeout(function(expected) { - // Only clear it if it's the expected grab (i.e. no one - // else has initiated a new grab) - if (Util._captureIndex === expected) { - Util._captureElem = null; - } - }, 0, Util._captureIndex); - - Util._captureObserver.disconnect(); - - var captureElem = document.getElementById("noVNC_mouse_capture_elem"); - captureElem.style.display = "none"; - - window.removeEventListener('mousemove', Util._captureProxy); - window.removeEventListener('mouseup', Util._captureProxy); - - window.removeEventListener('touchmove', Util._captureProxy); - window.removeEventListener('touchend', Util._captureProxy); - } -}; - -export default Util; diff --git a/core/util/browsers.js b/core/util/browsers.js new file mode 100644 index 00000000..ab5e09f3 --- /dev/null +++ b/core/util/browsers.js @@ -0,0 +1,120 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +import * as Log from './logging.js'; + +// Set browser engine versions. Based on mootools. +const Features = {xpath: !!(document.evaluate), query: !!(document.querySelector)}; + +// 'presto': (function () { return (!window.opera) ? false : true; }()), +var detectPresto = function () { + return !!window.opera; +}; + +// 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); +var detectTrident = function () { + if (!window.ActiveXObject) { + return false; + } else { + if (window.XMLHttpRequest) { + return (document.querySelectorAll) ? 6 : 5; + } else { + return 4; + } + } +}; + +// 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Features.xpath) ? ((Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()), +var detectInitialWebkit = function () { + try { + if (navigator.taintEnabled) { + return false; + } else { + if (Features.xpath) { + return (Features.query) ? 525 : 420; + } else { + return 419; + } + } + } catch (e) { + return false; + } +}; + +var detectActualWebkit = function (initial_ver) { + var re = /WebKit\/([0-9\.]*) /; + var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1]; + return parseFloat(str_ver, 10); +}; + +// 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }()) +var detectGecko = function () { + /* jshint -W041 */ + if (!document.getBoxObjectFor && window.mozInnerScreenX == null) { + return false; + } else { + return (document.getElementsByClassName) ? 19 : 18; + } + /* jshint +W041 */ +}; + +const isWebkitInitial = detectInitialWebkit(); + +export const Engine = { + // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference) + //'presto': (function() { + // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), + 'presto': detectPresto(), + 'trident': detectTrident(), + 'webkit': isWebkitInitial ? detectActualWebkit(isWebkitInitial) : false, + 'gecko': detectGecko() +}; + +// Touch detection +export var isTouchDevice = ('ontouchstart' in document.documentElement) || + // requried for Chrome debugger + (document.ontouchstart !== undefined) || + // required for MS Surface + (navigator.maxTouchPoints > 0) || + (navigator.msMaxTouchPoints > 0); +window.addEventListener('touchstart', function onFirstTouch() { + isTouchDevice = true; + window.removeEventListener('touchstart', onFirstTouch, false); +}, false); + +var _cursor_uris_supported = null; + +export function browserSupportsCursorURIs () { + if (_cursor_uris_supported === null) { + try { + var target = document.createElement('canvas'); + target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default'; + + if (target.style.cursor) { + Log.Info("Data URI scheme cursor supported"); + _cursor_uris_supported = true; + } else { + Log.Warn("Data URI scheme cursor not supported"); + _cursor_uris_supported = false; + } + } catch (exc) { + Log.Error("Data URI scheme cursor test exception: " + exc); + _cursor_uris_supported = false; + } + } + + return _cursor_uris_supported; +}; + +export function _forceCursorURIs(enabled) { + if (enabled === undefined || enabled) { + _cursor_uris_supported = true; + } else { + _cursor_uris_supported = false; + } +} diff --git a/core/util/events.js b/core/util/events.js new file mode 100644 index 00000000..8fdf6aa6 --- /dev/null +++ b/core/util/events.js @@ -0,0 +1,161 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/* + * Cross-browser event and position routines + */ + +import * as Log from './logging.js'; + +export function getPointerEvent (e) { + return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e; +}; + +export function stopEvent (e) { + e.stopPropagation(); + e.preventDefault(); +}; + +// Emulate Element.setCapture() when not supported +var _captureRecursion = false; +var _captureElem = null; +const _captureProxy = function (e) { + // Recursion protection as we'll see our own event + if (_captureRecursion) return; + + // Clone the event as we cannot dispatch an already dispatched event + var newEv = new e.constructor(e.type, e); + + _captureRecursion = true; + _captureElem.dispatchEvent(newEv); + _captureRecursion = false; + + // Avoid double events + e.stopPropagation(); + + // Respect the wishes of the redirected event handlers + if (newEv.defaultPrevented) { + e.preventDefault(); + } + + // Implicitly release the capture on button release + if ((e.type === "mouseup") || (e.type === "touchend")) { + releaseCapture(); + } +}; + +// Follow cursor style of target element +const _captureElemChanged = function() { + var captureElem = document.getElementById("noVNC_mouse_capture_elem"); + captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor; +}; +const _captureObserver = new MutationObserver(_captureElemChanged); + +var _captureIndex = 0; + +export function setCapture (elem) { + if (elem.setCapture) { + + elem.setCapture(); + + // IE releases capture on 'click' events which might not trigger + elem.addEventListener('mouseup', releaseCapture); + elem.addEventListener('touchend', releaseCapture); + + } else { + // Release any existing capture in case this method is + // called multiple times without coordination + releaseCapture(); + + // Safari on iOS 9 has a broken constructor for TouchEvent. + // We are fine in this case however, since Safari seems to + // have some sort of implicit setCapture magic anyway. + if (window.TouchEvent !== undefined) { + try { + new TouchEvent("touchstart"); + } catch (TypeError) { + return; + } + } + + var captureElem = document.getElementById("noVNC_mouse_capture_elem"); + + if (captureElem === null) { + captureElem = document.createElement("div"); + captureElem.id = "noVNC_mouse_capture_elem"; + captureElem.style.position = "fixed"; + captureElem.style.top = "0px"; + captureElem.style.left = "0px"; + captureElem.style.width = "100%"; + captureElem.style.height = "100%"; + captureElem.style.zIndex = 10000; + captureElem.style.display = "none"; + document.body.appendChild(captureElem); + + // This is to make sure callers don't get confused by having + // our blocking element as the target + captureElem.addEventListener('contextmenu', _captureProxy); + + captureElem.addEventListener('mousemove', _captureProxy); + captureElem.addEventListener('mouseup', _captureProxy); + + captureElem.addEventListener('touchmove', _captureProxy); + captureElem.addEventListener('touchend', _captureProxy); + } + + _captureElem = elem; + _captureIndex++; + + // Track cursor and get initial cursor + _captureObserver.observe(elem, {attributes:true}); + _captureElemChanged(); + + captureElem.style.display = null; + + // We listen to events on window in order to keep tracking if it + // happens to leave the viewport + window.addEventListener('mousemove', _captureProxy); + window.addEventListener('mouseup', _captureProxy); + + window.addEventListener('touchmove', _captureProxy); + window.addEventListener('touchend', _captureProxy); + } +}; + +export function releaseCapture () { + if (document.releaseCapture) { + + document.releaseCapture(); + + } else { + if (!_captureElem) { + return; + } + + // There might be events already queued, so we need to wait for + // them to flush. E.g. contextmenu in Microsoft Edge + window.setTimeout(function(expected) { + // Only clear it if it's the expected grab (i.e. no one + // else has initiated a new grab) + if (_captureIndex === expected) { + _captureElem = null; + } + }, 0, _captureIndex); + + _captureObserver.disconnect(); + + var captureElem = document.getElementById("noVNC_mouse_capture_elem"); + captureElem.style.display = "none"; + + window.removeEventListener('mousemove', _captureProxy); + window.removeEventListener('mouseup', _captureProxy); + + window.removeEventListener('touchmove', _captureProxy); + window.removeEventListener('touchend', _captureProxy); + } +}; diff --git a/core/util/localization.js b/core/util/localization.js new file mode 100644 index 00000000..2a27a69c --- /dev/null +++ b/core/util/localization.js @@ -0,0 +1,170 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/* + * Localization Utilities + */ + +export function Localizer() { + // Currently configured language + this.language = 'en'; + + // Current dictionary of translations + this.dictionary = undefined; +} + +Localizer.prototype = { + // Configure suitable language based on user preferences + setup: function (supportedLanguages) { + var userLanguages; + + this.language = 'en'; // Default: US English + + /* + * Navigator.languages only available in Chrome (32+) and FireFox (32+) + * Fall back to navigator.language for other browsers + */ + if (typeof window.navigator.languages == 'object') { + userLanguages = window.navigator.languages; + } else { + userLanguages = [navigator.language || navigator.userLanguage]; + } + + for (var i = 0;i < userLanguages.length;i++) { + var userLang = userLanguages[i]; + userLang = userLang.toLowerCase(); + userLang = userLang.replace("_", "-"); + userLang = userLang.split("-"); + + // Built-in default? + if ((userLang[0] === 'en') && + ((userLang[1] === undefined) || (userLang[1] === 'us'))) { + return; + } + + // First pass: perfect match + for (var j = 0;j < supportedLanguages.length;j++) { + var supLang = supportedLanguages[j]; + supLang = supLang.toLowerCase(); + supLang = supLang.replace("_", "-"); + supLang = supLang.split("-"); + + if (userLang[0] !== supLang[0]) + continue; + if (userLang[1] !== supLang[1]) + continue; + + this.language = supportedLanguages[j]; + return; + } + + // Second pass: fallback + for (var j = 0;j < supportedLanguages.length;j++) { + supLang = supportedLanguages[j]; + supLang = supLang.toLowerCase(); + supLang = supLang.replace("_", "-"); + supLang = supLang.split("-"); + + if (userLang[0] !== supLang[0]) + continue; + if (supLang[1] !== undefined) + continue; + + this.language = supportedLanguages[j]; + return; + } + } + }, + + // Retrieve localised text + get: function (id) { + if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) { + return this.dictionary[id]; + } else { + return id; + } + }, + + // Traverses the DOM and translates relevant fields + // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate + translateDOM: function () { + var self = this; + function process(elem, enabled) { + function isAnyOf(searchElement, items) { + return items.indexOf(searchElement) !== -1; + } + + function translateAttribute(elem, attr) { + var str = elem.getAttribute(attr); + str = self.get(str); + elem.setAttribute(attr, str); + } + + function translateTextNode(node) { + var str = node.data.trim(); + str = self.get(str); + node.data = str; + } + + if (elem.hasAttribute("translate")) { + if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) { + enabled = true; + } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) { + enabled = false; + } + } + + if (enabled) { + if (elem.hasAttribute("abbr") && + elem.tagName === "TH") { + translateAttribute(elem, "abbr"); + } + if (elem.hasAttribute("alt") && + isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) { + translateAttribute(elem, "alt"); + } + if (elem.hasAttribute("download") && + isAnyOf(elem.tagName, ["A", "AREA"])) { + translateAttribute(elem, "download"); + } + if (elem.hasAttribute("label") && + isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP", + "OPTION", "TRACK"])) { + translateAttribute(elem, "label"); + } + // FIXME: Should update "lang" + if (elem.hasAttribute("placeholder") && + isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) { + translateAttribute(elem, "placeholder"); + } + if (elem.hasAttribute("title")) { + translateAttribute(elem, "title"); + } + if (elem.hasAttribute("value") && + elem.tagName === "INPUT" && + isAnyOf(elem.getAttribute("type"), ["reset", "button"])) { + translateAttribute(elem, "value"); + } + } + + for (var i = 0;i < elem.childNodes.length;i++) { + let node = elem.childNodes[i]; + if (node.nodeType === node.ELEMENT_NODE) { + process(node, enabled); + } else if (node.nodeType === node.TEXT_NODE && enabled) { + translateTextNode(node); + } + } + } + + process(document.body, true); + }, +} + +export const l10n = new Localizer(); +export default l10n.get.bind(l10n); diff --git a/core/util/logging.js b/core/util/logging.js new file mode 100644 index 00000000..342bfa7c --- /dev/null +++ b/core/util/logging.js @@ -0,0 +1,53 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/* + * Logging/debug routines + */ + +var _log_level = 'warn'; + +var Debug = function (msg) {}; +var Info = function (msg) {}; +var Warn = function (msg) {}; +var Error = function (msg) {}; + +export function init_logging (level) { + if (typeof level === 'undefined') { + level = _log_level; + } else { + _log_level = level; + } + + Debug = Info = Warn = Error = function (msg) {}; + if (typeof window.console !== "undefined") { + /* jshint -W086 */ + switch (level) { + case 'debug': + Debug = console.debug.bind(window.console); + case 'info': + Info = console.info.bind(window.console); + case 'warn': + Warn = console.warn.bind(window.console); + case 'error': + Error = console.error.bind(window.console); + case 'none': + break; + default: + throw new Error("invalid logging type '" + level + "'"); + } + /* jshint +W086 */ + } +}; +export function get_logging () { + return _log_level; +}; +export { Debug, Info, Warn, Error }; + +// Initialize logging level +init_logging(); diff --git a/core/util/properties.js b/core/util/properties.js new file mode 100644 index 00000000..e1d607e0 --- /dev/null +++ b/core/util/properties.js @@ -0,0 +1,138 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/* + * Getter/Setter Creation Utilities + */ + +import * as Log from './logging.js'; + +function make_property (proto, name, mode, type) { + "use strict"; + + var getter; + if (type === 'arr') { + getter = function (idx) { + if (typeof idx !== 'undefined') { + return this['_' + name][idx]; + } else { + return this['_' + name]; + } + }; + } else { + getter = function () { + return this['_' + name]; + }; + } + + var make_setter = function (process_val) { + if (process_val) { + return function (val, idx) { + if (typeof idx !== 'undefined') { + this['_' + name][idx] = process_val(val); + } else { + this['_' + name] = process_val(val); + } + }; + } else { + return function (val, idx) { + if (typeof idx !== 'undefined') { + this['_' + name][idx] = val; + } else { + this['_' + name] = val; + } + }; + } + }; + + var setter; + if (type === 'bool') { + setter = make_setter(function (val) { + if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) { + return false; + } else { + return true; + } + }); + } else if (type === 'int') { + setter = make_setter(function (val) { return parseInt(val, 10); }); + } else if (type === 'float') { + setter = make_setter(parseFloat); + } else if (type === 'str') { + setter = make_setter(String); + } else if (type === 'func') { + setter = make_setter(function (val) { + if (!val) { + return function () {}; + } else { + return val; + } + }); + } else if (type === 'arr' || type === 'dom' || type == 'raw') { + setter = make_setter(); + } else { + throw new Error('Unknown property type ' + type); // some sanity checking + } + + // set the getter + if (typeof proto['get_' + name] === 'undefined') { + proto['get_' + name] = getter; + } + + // set the setter if needed + if (typeof proto['set_' + name] === 'undefined') { + if (mode === 'rw') { + proto['set_' + name] = setter; + } else if (mode === 'wo') { + proto['set_' + name] = function (val, idx) { + if (typeof this['_' + name] !== 'undefined') { + throw new Error(name + " can only be set once"); + } + setter.call(this, val, idx); + }; + } + } + + // make a special setter that we can use in set defaults + proto['_raw_set_' + name] = function (val, idx) { + setter.call(this, val, idx); + //delete this['_init_set_' + name]; // remove it after use + }; +}; + +export function make_properties (constructor, arr) { + "use strict"; + for (var i = 0; i < arr.length; i++) { + make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]); + } +}; + +export function set_defaults (obj, conf, defaults) { + var defaults_keys = Object.keys(defaults); + var conf_keys = Object.keys(conf); + var keys_obj = {}; + var i; + for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; } + for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; } + var keys = Object.keys(keys_obj); + + for (i = 0; i < keys.length; i++) { + var setter = obj['_raw_set_' + keys[i]]; + if (!setter) { + Log.Warn('Invalid property ' + keys[i]); + continue; + } + + if (keys[i] in conf) { + setter.call(obj, conf[keys[i]]); + } else { + setter.call(obj, defaults[keys[i]]); + } + } +}; + diff --git a/core/util/strings.js b/core/util/strings.js new file mode 100644 index 00000000..00a6156c --- /dev/null +++ b/core/util/strings.js @@ -0,0 +1,15 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/* + * Decode from UTF-8 + */ +export function decodeUTF8 (utf8string) { + "use strict"; + return decodeURIComponent(escape(utf8string)); +}; diff --git a/core/websock.js b/core/websock.js index 7cb04667..78809a1a 100644 --- a/core/websock.js +++ b/core/websock.js @@ -12,8 +12,7 @@ * read binary data off of the receive queue. */ -import Util from "./util.js"; - +import * as Log from './util/logging.js'; /*jslint browser: true, bitwise: true */ /*global Util*/ @@ -43,313 +42,310 @@ export default function Websock() { }; }; -(function () { - "use strict"; - // this has performance issues in some versions Chromium, and - // doesn't gain a tremendous amount of performance increase in Firefox - // at the moment. It may be valuable to turn it on in the future. - var ENABLE_COPYWITHIN = false; +// this has performance issues in some versions Chromium, and +// doesn't gain a tremendous amount of performance increase in Firefox +// at the moment. It may be valuable to turn it on in the future. +var ENABLE_COPYWITHIN = false; - var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB +var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB - var typedArrayToString = (function () { - // This is only for PhantomJS, which doesn't like apply-ing - // with Typed Arrays - try { - var arr = new Uint8Array([1, 2, 3]); - String.fromCharCode.apply(null, arr); - return function (a) { return String.fromCharCode.apply(null, a); }; - } catch (ex) { - return function (a) { - return String.fromCharCode.apply( - null, Array.prototype.slice.call(a)); - }; - } - })(); - - Websock.prototype = { - // Getters and Setters - get_sQ: function () { - return this._sQ; - }, - - get_rQ: function () { - return this._rQ; - }, - - get_rQi: function () { - return this._rQi; - }, - - set_rQi: function (val) { - this._rQi = val; - }, - - // Receive Queue - rQlen: function () { - return this._rQlen - this._rQi; - }, - - rQpeek8: function () { - return this._rQ[this._rQi]; - }, - - rQshift8: function () { - return this._rQ[this._rQi++]; - }, - - rQskip8: function () { - this._rQi++; - }, - - rQskipBytes: function (num) { - this._rQi += num; - }, - - // TODO(directxman12): test performance with these vs a DataView - rQshift16: function () { - return (this._rQ[this._rQi++] << 8) + - this._rQ[this._rQi++]; - }, - - rQshift32: function () { - return (this._rQ[this._rQi++] << 24) + - (this._rQ[this._rQi++] << 16) + - (this._rQ[this._rQi++] << 8) + - this._rQ[this._rQi++]; - }, - - rQshiftStr: function (len) { - if (typeof(len) === 'undefined') { len = this.rQlen(); } - var arr = new Uint8Array(this._rQ.buffer, this._rQi, len); - this._rQi += len; - return typedArrayToString(arr); - }, - - rQshiftBytes: function (len) { - if (typeof(len) === 'undefined') { len = this.rQlen(); } - this._rQi += len; - return new Uint8Array(this._rQ.buffer, this._rQi - len, len); - }, - - rQshiftTo: function (target, len) { - if (len === undefined) { len = this.rQlen(); } - // TODO: make this just use set with views when using a ArrayBuffer to store the rQ - target.set(new Uint8Array(this._rQ.buffer, this._rQi, len)); - this._rQi += len; - }, - - rQwhole: function () { - return new Uint8Array(this._rQ.buffer, 0, this._rQlen); - }, - - rQslice: function (start, end) { - if (end) { - return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); - } else { - return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start); - } - }, - - // Check to see if we must wait for 'num' bytes (default to FBU.bytes) - // to be available in the receive queue. Return true if we need to - // wait (and possibly print a debug message), otherwise false. - rQwait: function (msg, num, goback) { - var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call - if (rQlen < num) { - if (goback) { - if (this._rQi < goback) { - throw new Error("rQwait cannot backup " + goback + " bytes"); - } - this._rQi -= goback; - } - return true; // true means need more data - } - return false; - }, - - // Send Queue - - flush: function () { - if (this._websocket.bufferedAmount !== 0) { - Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount); - } - - if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) { - this._websocket.send(this._encode_message()); - this._sQlen = 0; - } - }, - - send: function (arr) { - this._sQ.set(arr, this._sQlen); - this._sQlen += arr.length; - this.flush(); - }, - - send_string: function (str) { - this.send(str.split('').map(function (chr) { - return chr.charCodeAt(0); - })); - }, - - // Event Handlers - off: function (evt) { - this._eventHandlers[evt] = function () {}; - }, - - on: function (evt, handler) { - this._eventHandlers[evt] = handler; - }, - - _allocate_buffers: function () { - this._rQ = new Uint8Array(this._rQbufferSize); - this._sQ = new Uint8Array(this._sQbufferSize); - }, - - init: function () { - this._allocate_buffers(); - this._rQi = 0; - this._websocket = null; - }, - - open: function (uri, protocols) { - var ws_schema = uri.match(/^([a-z]+):\/\//)[1]; - this.init(); - - this._websocket = new WebSocket(uri, protocols); - this._websocket.binaryType = 'arraybuffer'; - - this._websocket.onmessage = this._recv_message.bind(this); - this._websocket.onopen = (function () { - Util.Debug('>> WebSock.onopen'); - if (this._websocket.protocol) { - Util.Info("Server choose sub-protocol: " + this._websocket.protocol); - } - - this._eventHandlers.open(); - Util.Debug("<< WebSock.onopen"); - }).bind(this); - this._websocket.onclose = (function (e) { - Util.Debug(">> WebSock.onclose"); - this._eventHandlers.close(e); - Util.Debug("<< WebSock.onclose"); - }).bind(this); - this._websocket.onerror = (function (e) { - Util.Debug(">> WebSock.onerror: " + e); - this._eventHandlers.error(e); - Util.Debug("<< WebSock.onerror: " + e); - }).bind(this); - }, - - close: function () { - if (this._websocket) { - if ((this._websocket.readyState === WebSocket.OPEN) || - (this._websocket.readyState === WebSocket.CONNECTING)) { - Util.Info("Closing WebSocket connection"); - this._websocket.close(); - } - - this._websocket.onmessage = function (e) { return; }; - } - }, - - // private methods - _encode_message: function () { - // Put in a binary arraybuffer - // according to the spec, you can send ArrayBufferViews with the send method - return new Uint8Array(this._sQ.buffer, 0, this._sQlen); - }, - - _expand_compact_rQ: function (min_fit) { - var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2; - if (resizeNeeded) { - if (!min_fit) { - // just double the size if we need to do compaction - this._rQbufferSize *= 2; - } else { - // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8 - this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8; - } - } - - // we don't want to grow unboundedly - if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { - this._rQbufferSize = MAX_RQ_GROW_SIZE; - if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) { - throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit"); - } - } - - if (resizeNeeded) { - var old_rQbuffer = this._rQ.buffer; - this._rQmax = this._rQbufferSize / 8; - this._rQ = new Uint8Array(this._rQbufferSize); - this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi)); - } else { - if (ENABLE_COPYWITHIN) { - this._rQ.copyWithin(0, this._rQi); - } else { - this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi)); - } - } - - this._rQlen = this._rQlen - this._rQi; - this._rQi = 0; - }, - - _decode_message: function (data) { - // push arraybuffer values onto the end - var u8 = new Uint8Array(data); - if (u8.length > this._rQbufferSize - this._rQlen) { - this._expand_compact_rQ(u8.length); - } - this._rQ.set(u8, this._rQlen); - this._rQlen += u8.length; - }, - - _recv_message: function (e) { - try { - this._decode_message(e.data); - if (this.rQlen() > 0) { - this._eventHandlers.message(); - // Compact the receive queue - if (this._rQlen == this._rQi) { - this._rQlen = 0; - this._rQi = 0; - } else if (this._rQlen > this._rQmax) { - this._expand_compact_rQ(); - } - } else { - Util.Debug("Ignoring empty message"); - } - } catch (exc) { - var exception_str = ""; - if (exc.name) { - exception_str += "\n name: " + exc.name + "\n"; - exception_str += " message: " + exc.message + "\n"; - } - - if (typeof exc.description !== 'undefined') { - exception_str += " description: " + exc.description + "\n"; - } - - if (typeof exc.stack !== 'undefined') { - exception_str += exc.stack; - } - - if (exception_str.length > 0) { - Util.Error("recv_message, caught exception: " + exception_str); - } else { - Util.Error("recv_message, caught exception: " + exc); - } - - if (typeof exc.name !== 'undefined') { - this._eventHandlers.error(exc.name + ": " + exc.message); - } else { - this._eventHandlers.error(exc); - } - } - } - }; +var typedArrayToString = (function () { + // This is only for PhantomJS, which doesn't like apply-ing + // with Typed Arrays + try { + var arr = new Uint8Array([1, 2, 3]); + String.fromCharCode.apply(null, arr); + return function (a) { return String.fromCharCode.apply(null, a); }; + } catch (ex) { + return function (a) { + return String.fromCharCode.apply( + null, Array.prototype.slice.call(a)); + }; + } })(); + +Websock.prototype = { + // Getters and Setters + get_sQ: function () { + return this._sQ; + }, + + get_rQ: function () { + return this._rQ; + }, + + get_rQi: function () { + return this._rQi; + }, + + set_rQi: function (val) { + this._rQi = val; + }, + + // Receive Queue + rQlen: function () { + return this._rQlen - this._rQi; + }, + + rQpeek8: function () { + return this._rQ[this._rQi]; + }, + + rQshift8: function () { + return this._rQ[this._rQi++]; + }, + + rQskip8: function () { + this._rQi++; + }, + + rQskipBytes: function (num) { + this._rQi += num; + }, + + // TODO(directxman12): test performance with these vs a DataView + rQshift16: function () { + return (this._rQ[this._rQi++] << 8) + + this._rQ[this._rQi++]; + }, + + rQshift32: function () { + return (this._rQ[this._rQi++] << 24) + + (this._rQ[this._rQi++] << 16) + + (this._rQ[this._rQi++] << 8) + + this._rQ[this._rQi++]; + }, + + rQshiftStr: function (len) { + if (typeof(len) === 'undefined') { len = this.rQlen(); } + var arr = new Uint8Array(this._rQ.buffer, this._rQi, len); + this._rQi += len; + return typedArrayToString(arr); + }, + + rQshiftBytes: function (len) { + if (typeof(len) === 'undefined') { len = this.rQlen(); } + this._rQi += len; + return new Uint8Array(this._rQ.buffer, this._rQi - len, len); + }, + + rQshiftTo: function (target, len) { + if (len === undefined) { len = this.rQlen(); } + // TODO: make this just use set with views when using a ArrayBuffer to store the rQ + target.set(new Uint8Array(this._rQ.buffer, this._rQi, len)); + this._rQi += len; + }, + + rQwhole: function () { + return new Uint8Array(this._rQ.buffer, 0, this._rQlen); + }, + + rQslice: function (start, end) { + if (end) { + return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); + } else { + return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start); + } + }, + + // Check to see if we must wait for 'num' bytes (default to FBU.bytes) + // to be available in the receive queue. Return true if we need to + // wait (and possibly print a debug message), otherwise false. + rQwait: function (msg, num, goback) { + var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call + if (rQlen < num) { + if (goback) { + if (this._rQi < goback) { + throw new Error("rQwait cannot backup " + goback + " bytes"); + } + this._rQi -= goback; + } + return true; // true means need more data + } + return false; + }, + + // Send Queue + + flush: function () { + if (this._websocket.bufferedAmount !== 0) { + Log.Debug("bufferedAmount: " + this._websocket.bufferedAmount); + } + + if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) { + this._websocket.send(this._encode_message()); + this._sQlen = 0; + } + }, + + send: function (arr) { + this._sQ.set(arr, this._sQlen); + this._sQlen += arr.length; + this.flush(); + }, + + send_string: function (str) { + this.send(str.split('').map(function (chr) { + return chr.charCodeAt(0); + })); + }, + + // Event Handlers + off: function (evt) { + this._eventHandlers[evt] = function () {}; + }, + + on: function (evt, handler) { + this._eventHandlers[evt] = handler; + }, + + _allocate_buffers: function () { + this._rQ = new Uint8Array(this._rQbufferSize); + this._sQ = new Uint8Array(this._sQbufferSize); + }, + + init: function () { + this._allocate_buffers(); + this._rQi = 0; + this._websocket = null; + }, + + open: function (uri, protocols) { + var ws_schema = uri.match(/^([a-z]+):\/\//)[1]; + this.init(); + + this._websocket = new WebSocket(uri, protocols); + this._websocket.binaryType = 'arraybuffer'; + + this._websocket.onmessage = this._recv_message.bind(this); + this._websocket.onopen = (function () { + Log.Debug('>> WebSock.onopen'); + if (this._websocket.protocol) { + Log.Info("Server choose sub-protocol: " + this._websocket.protocol); + } + + this._eventHandlers.open(); + Log.Debug("<< WebSock.onopen"); + }).bind(this); + this._websocket.onclose = (function (e) { + Log.Debug(">> WebSock.onclose"); + this._eventHandlers.close(e); + Log.Debug("<< WebSock.onclose"); + }).bind(this); + this._websocket.onerror = (function (e) { + Log.Debug(">> WebSock.onerror: " + e); + this._eventHandlers.error(e); + Log.Debug("<< WebSock.onerror: " + e); + }).bind(this); + }, + + close: function () { + if (this._websocket) { + if ((this._websocket.readyState === WebSocket.OPEN) || + (this._websocket.readyState === WebSocket.CONNECTING)) { + Log.Info("Closing WebSocket connection"); + this._websocket.close(); + } + + this._websocket.onmessage = function (e) { return; }; + } + }, + + // private methods + _encode_message: function () { + // Put in a binary arraybuffer + // according to the spec, you can send ArrayBufferViews with the send method + return new Uint8Array(this._sQ.buffer, 0, this._sQlen); + }, + + _expand_compact_rQ: function (min_fit) { + var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2; + if (resizeNeeded) { + if (!min_fit) { + // just double the size if we need to do compaction + this._rQbufferSize *= 2; + } else { + // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8 + this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8; + } + } + + // we don't want to grow unboundedly + if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { + this._rQbufferSize = MAX_RQ_GROW_SIZE; + if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) { + throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit"); + } + } + + if (resizeNeeded) { + var old_rQbuffer = this._rQ.buffer; + this._rQmax = this._rQbufferSize / 8; + this._rQ = new Uint8Array(this._rQbufferSize); + this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi)); + } else { + if (ENABLE_COPYWITHIN) { + this._rQ.copyWithin(0, this._rQi); + } else { + this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi)); + } + } + + this._rQlen = this._rQlen - this._rQi; + this._rQi = 0; + }, + + _decode_message: function (data) { + // push arraybuffer values onto the end + var u8 = new Uint8Array(data); + if (u8.length > this._rQbufferSize - this._rQlen) { + this._expand_compact_rQ(u8.length); + } + this._rQ.set(u8, this._rQlen); + this._rQlen += u8.length; + }, + + _recv_message: function (e) { + try { + this._decode_message(e.data); + if (this.rQlen() > 0) { + this._eventHandlers.message(); + // Compact the receive queue + if (this._rQlen == this._rQi) { + this._rQlen = 0; + this._rQi = 0; + } else if (this._rQlen > this._rQmax) { + this._expand_compact_rQ(); + } + } else { + Log.Debug("Ignoring empty message"); + } + } catch (exc) { + var exception_str = ""; + if (exc.name) { + exception_str += "\n name: " + exc.name + "\n"; + exception_str += " message: " + exc.message + "\n"; + } + + if (typeof exc.description !== 'undefined') { + exception_str += " description: " + exc.description + "\n"; + } + + if (typeof exc.stack !== 'undefined') { + exception_str += exc.stack; + } + + if (exception_str.length > 0) { + Log.Error("recv_message, caught exception: " + exception_str); + } else { + Log.Error("recv_message, caught exception: " + exc); + } + + if (typeof exc.name !== 'undefined') { + this._eventHandlers.error(exc.name + ": " + exc.message); + } else { + this._eventHandlers.error(exc); + } + } + } +}; From dfae3209ebcde77a341addbb55514d8285499d73 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Sun, 5 Feb 2017 12:34:47 -0500 Subject: [PATCH 10/19] Update tests to work with new structure This updates the tests to work with the new structure, and removes the old `utils/run_from_console.js` files in favor of just using Karma directly. The Karma debug page now displays the normal mocha HTML, so we can use that instead of the HTML generation functionality of the old test runner. Note that PhantomJS does not work at the moment (PhantomJS 1.5 should make it possible to test on PhantomJS again). --- .travis.yml | 1 - CONTRIBUTING.md | 18 +- karma.conf.js | 99 +- package.json | 15 +- tests/fake.websocket.js | 150 +- tests/karma-test-main.js | 18 + tests/run_from_console.casper.js | 114 - tests/run_from_console.js | 360 -- tests/run_from_console.zombie.js | 82 - tests/test.base64.js | 3 +- tests/test.display.js | 25 +- tests/test.helper.js | 7 +- tests/test.keyboard.js | 4 +- tests/test.rfb.js | 11 +- tests/test.util.js | 60 +- tests/test.websock.js | 10 +- vendor/sinon.js | 6463 ++++++++++++++++++++++++++++++ 17 files changed, 6658 insertions(+), 782 deletions(-) create mode 100644 tests/karma-test-main.js delete mode 100644 tests/run_from_console.casper.js delete mode 100755 tests/run_from_console.js delete mode 100644 tests/run_from_console.zombie.js create mode 100644 vendor/sinon.js diff --git a/.travis.yml b/.travis.yml index a7fe4233..ba5c50be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ node_js: - '6.1' env: matrix: - - TEST_BROWSER_NAME=PhantomJS - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11' - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11' - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4bcea104..2d2e68cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,20 +35,8 @@ Contributing Guidelines Running the unit tests ---------------------- -There are two ways to run the unit tests. For both ways, you should first run -`npm install` (not as root). - -The first way to run the tests is to run `npm test`. This will run all the -tests in the headless PhantomJS browser (which uses WebKit). - -The second way to run the tests is using the `tests/run_from_console.js` file. -This way is a bit more flexible, and can provide more information about what -went wrong. To run all the tests, simply run `tests/run_from_console.js`. -To run a specific test file, you can use the `-t path/to/test/file.js` option. -If you wish to simply generate the HTML for the test, use the `-g` option, and -the path to the temporary HTML file will be written to standard out. To open -this file in your default browser automatically, pass the `-o` option as well. -More information can be found by passing the `--help` or `-h` option. - +We use Karma to run our tests. You can launch karma manually, or simply +run `npm test`. The Karma debug page will display the tests in normal +mocha form, if you need it. Thanks, and happy coding! diff --git a/karma.conf.js b/karma.conf.js index 5f3c20b9..821007ee 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,56 +1,6 @@ // Karma configuration module.exports = function(config) { - /*var customLaunchers = { - sl_chrome_win7: { - base: 'SauceLabs', - browserName: 'chrome', - platform: 'Windows 7' - }, - - sl_firefox30_linux: { - base: 'SauceLabs', - browserName: 'firefox', - version: '30', - platform: 'Linux' - }, - - sl_firefox26_linux: { - base: 'SauceLabs', - browserName: 'firefox', - version: 26, - platform: 'Linux' - }, - - sl_windows7_ie10: { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 7', - version: '10' - }, - - sl_windows81_ie11: { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 8.1', - version: '11' - }, - - sl_osxmavericks_safari7: { - base: 'SauceLabs', - browserName: 'safari', - platform: 'OS X 10.9', - version: '7' - }, - - sl_osxmtnlion_safari6: { - base: 'SauceLabs', - browserName: 'safari', - platform: 'OS X 10.8', - version: '6' - } - };*/ - var customLaunchers = {}; var browsers = []; var useSauce = false; @@ -93,7 +43,8 @@ module.exports = function(config) { browsers = Object.keys(customLaunchers); } else { useSauce = false; - browsers = ['PhantomJS']; + //browsers = ['PhantomJS']; + browsers = []; } var my_conf = { @@ -103,39 +54,30 @@ module.exports = function(config) { // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha', 'sinon', 'chai', 'sinon-chai'], - + frameworks: ['requirejs', 'mocha', 'chai'], // list of files / patterns to load in the browser (loaded in order) files: [ - 'tests/fake.*.js', - 'tests/assertions.js', - 'core/util.js', // load first to avoid issues, since methods are called immediately - //'../core/*.js', - 'core/base64.js', - 'core/input/keysym.js', - 'core/input/keysymdef.js', - 'core/input/xtscancodes.js', - 'core/input/util.js', - 'core/input/devices.js', - 'core/websock.js', - 'core/rfb.js', - 'core/des.js', - 'core/display.js', - 'core/inflator.js', - 'tests/test.*.js' + { pattern: 'vendor/sinon.js', included: false }, + { pattern: 'node_modules/sinon-chai/lib/sinon-chai.js', included: false }, + { pattern: 'core/**/*.js', included: false }, + { pattern: 'vendor/pako/**/*.js', included: false }, + { pattern: 'tests/test.*.js', included: false }, + { pattern: 'tests/fake.*.js', included: false }, + { pattern: 'tests/assertions.js', included: false }, + 'tests/karma-test-main.js', ], client: { mocha: { + // replace Karma debug page with mocha display + 'reporter': 'html', 'ui': 'bdd' } }, // list of files to exclude exclude: [ - '../tests/playback.js', - '../app/ui.js' ], customLaunchers: customLaunchers, @@ -147,14 +89,24 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - + 'core/**/*.js': ['babel'], + 'tests/test.*.js': ['babel'], + 'tests/fake.*.js': ['babel'], + 'tests/assertions.js': ['babel'], + 'vendor/pako/**/*.js': ['babel'], }, + babelPreprocessor: { + options: { + plugins: ['transform-es2015-modules-amd', 'syntax-dynamic-import'], + sourceMap: 'inline', + }, + }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['mocha', 'saucelabs'], + reporters: ['mocha'], // web server port @@ -186,6 +138,7 @@ module.exports = function(config) { }; if (useSauce) { + my_conf.reporters.push('saucelabs'); my_conf.captureTimeout = 0; // use SL timeout my_conf.sauceLabs = { testName: 'noVNC Tests (all)', diff --git a/package.json b/package.json index d51f07de..5bc89240 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ }, "homepage": "https://github.com/kanaka/noVNC", "devDependencies": { - "ansi": "^0.3.1", "babel-core": "^6.22.1", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-syntax-dynamic-import": "^6.18.0", @@ -38,27 +37,21 @@ "babelify": "^7.3.0", "browser-es-module-loader": "^0.4.1", "browserify": "^13.1.0", - "casperjs": "^1.1.3", "chai": "^3.5.0", "commander": "^2.9.0", "fs-extra": "^1.0.0", "jsdom": "*", "karma": "^1.3.0", + "karma-babel-preprocessor": "^6.0.1", "karma-chai": "^0.1.0", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.0", - "karma-phantomjs-launcher": "^1.0.2", "karma-sauce-launcher": "^1.0.0", - "karma-sinon": "^1.0.5", - "karma-sinon-chai-latest": "^0.1.0", + "karma-requirejs": "^1.1.0", + "requirejs": "^2.3.2", "mocha": "^3.1.2", "node-getopt": "*", - "open": "^0.0.5", - "phantomjs-prebuilt": "^2.1.13", "po2json": "*", - "sinon": "^1.17.6", - "sinon-chai": "^2.8.0", - "spooky": "^0.2.5", - "temp": "^0.8.3" + "sinon-chai": "^2.8.0" } } diff --git a/tests/fake.websocket.js b/tests/fake.websocket.js index 21012059..eb4f203d 100644 --- a/tests/fake.websocket.js +++ b/tests/fake.websocket.js @@ -1,91 +1,87 @@ -var FakeWebSocket; - -(function () { - // PhantomJS can't create Event objects directly, so we need to use this - function make_event(name, props) { - var evt = document.createEvent('Event'); - evt.initEvent(name, true, true); - if (props) { - for (var prop in props) { - evt[prop] = props[prop]; - } +// PhantomJS can't create Event objects directly, so we need to use this +function make_event(name, props) { + var evt = document.createEvent('Event'); + evt.initEvent(name, true, true); + if (props) { + for (var prop in props) { + evt[prop] = props[prop]; } - return evt; + } + return evt; +} + +export default function FakeWebSocket (uri, protocols) { + this.url = uri; + this.binaryType = "arraybuffer"; + this.extensions = ""; + + if (!protocols || typeof protocols === 'string') { + this.protocol = protocols; + } else { + this.protocol = protocols[0]; } - FakeWebSocket = function (uri, protocols) { - this.url = uri; - this.binaryType = "arraybuffer"; - this.extensions = ""; + this._send_queue = new Uint8Array(20000); - if (!protocols || typeof protocols === 'string') { - this.protocol = protocols; + this.readyState = FakeWebSocket.CONNECTING; + this.bufferedAmount = 0; + + this.__is_fake = true; +}; + +FakeWebSocket.prototype = { + close: function (code, reason) { + this.readyState = FakeWebSocket.CLOSED; + if (this.onclose) { + this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true })); + } + }, + + send: function (data) { + if (this.protocol == 'base64') { + data = Base64.decode(data); } else { - this.protocol = protocols[0]; + data = new Uint8Array(data); } + this._send_queue.set(data, this.bufferedAmount); + this.bufferedAmount += data.length; + }, - this._send_queue = new Uint8Array(20000); - - this.readyState = FakeWebSocket.CONNECTING; + _get_sent_data: function () { + var res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount); this.bufferedAmount = 0; + return res; + }, - this.__is_fake = true; - }; - - FakeWebSocket.prototype = { - close: function (code, reason) { - this.readyState = FakeWebSocket.CLOSED; - if (this.onclose) { - this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true })); - } - }, - - send: function (data) { - if (this.protocol == 'base64') { - data = Base64.decode(data); - } else { - data = new Uint8Array(data); - } - this._send_queue.set(data, this.bufferedAmount); - this.bufferedAmount += data.length; - }, - - _get_sent_data: function () { - var res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount); - this.bufferedAmount = 0; - return res; - }, - - _open: function (data) { - this.readyState = FakeWebSocket.OPEN; - if (this.onopen) { - this.onopen(make_event('open')); - } - }, - - _receive_data: function (data) { - this.onmessage(make_event("message", { 'data': data })); + _open: function (data) { + this.readyState = FakeWebSocket.OPEN; + if (this.onopen) { + this.onopen(make_event('open')); } - }; + }, - FakeWebSocket.OPEN = WebSocket.OPEN; - FakeWebSocket.CONNECTING = WebSocket.CONNECTING; - FakeWebSocket.CLOSING = WebSocket.CLOSING; - FakeWebSocket.CLOSED = WebSocket.CLOSED; + _receive_data: function (data) { + this.onmessage(make_event("message", { 'data': data })); + } +}; - FakeWebSocket.__is_fake = true; +FakeWebSocket.OPEN = WebSocket.OPEN; +FakeWebSocket.CONNECTING = WebSocket.CONNECTING; +FakeWebSocket.CLOSING = WebSocket.CLOSING; +FakeWebSocket.CLOSED = WebSocket.CLOSED; - FakeWebSocket.replace = function () { - if (!WebSocket.__is_fake) { - var real_version = WebSocket; - WebSocket = FakeWebSocket; - FakeWebSocket.__real_version = real_version; - } - }; +FakeWebSocket.__is_fake = true; - FakeWebSocket.restore = function () { - if (WebSocket.__is_fake) { - WebSocket = WebSocket.__real_version; - } - }; -})(); +FakeWebSocket.replace = function () { + if (!WebSocket.__is_fake) { + var real_version = WebSocket; + WebSocket = FakeWebSocket; + FakeWebSocket.__real_version = real_version; + } +}; + +FakeWebSocket.restore = function () { + if (WebSocket.__is_fake) { + WebSocket = WebSocket.__real_version; + } +}; diff --git a/tests/karma-test-main.js b/tests/karma-test-main.js new file mode 100644 index 00000000..35b46973 --- /dev/null +++ b/tests/karma-test-main.js @@ -0,0 +1,18 @@ +var TEST_REGEXP = /test\..*\.js/; +var allTestFiles = []; + +Object.keys(window.__karma__.files).forEach(function (file) { + if (TEST_REGEXP.test(file)) { + // TODO: normalize? + allTestFiles.push(file); + } +}); + +require.config({ + baseUrl: '/base', + deps: allTestFiles, + callback: window.__karma__.start, + paths: { + 'sinon': 'vendor/sinon', + }, +}); diff --git a/tests/run_from_console.casper.js b/tests/run_from_console.casper.js deleted file mode 100644 index ba8546b9..00000000 --- a/tests/run_from_console.casper.js +++ /dev/null @@ -1,114 +0,0 @@ -var Spooky = require('spooky'); -var path = require('path'); - -var phantom_path = require('phantomjs-prebuilt').path; -var casper_path = path.resolve(__dirname, '../node_modules/casperjs/bin/casperjs'); -process.env.PHANTOMJS_EXECUTABLE = phantom_path; -var casper_opts = { - child: { - transport: 'http', - command: casper_path - }, - casper: { - logLevel: 'debug', - verbose: true - } -}; - -var provide_emitter = function(file_paths, debug_port) { - if (debug_port) { - casper_opts.child['remote-debugger-port'] = debug_port; - var debug_url = ('https://localhost:' + debug_port + - '/webkit/inspector/inspector.html?page='); - console.info('[remote-debugger] Navigate to ' + debug_url + '1 and ' + - 'run `__run();` in the console to continue loading.' + - '\n[remote-debugger] Navigate to ' + debug_url + '2 to ' + - 'view the actual page source.\n' + - '[remote-debugger] Use the `debugger;` statement to ' + - 'trigger an initial breakpoint.'); - } - - var spooky = new Spooky(casper_opts, function(err) { - if (err) { - if (err.stack) console.warn(err.stack); - else console.warn(err); - return; - } - spooky.start('about:blank'); - - file_paths.forEach(function(file_path, path_ind) { - spooky.thenOpen('file://'+file_path); - spooky.waitFor(function() { - return this.getGlobal('__mocha_done') === true; - }, - [{ path_ind: path_ind }, function() { - var res_json = { - file_ind: path_ind - }; - - res_json.num_tests = this.evaluate(function() { return document.querySelectorAll('li.test').length; }); - res_json.num_passes = this.evaluate(function() { return document.querySelectorAll('li.test.pass').length; }); - res_json.num_fails = this.evaluate(function() { return document.querySelectorAll('li.test.fail').length; }); - res_json.num_slow = this.evaluate(function() { return document.querySelectorAll('li.test.pass:not(.fast):not(.pending)').length; }); - res_json.num_skipped = this.evaluate(function () { return document.querySelectorAll('li.test.pending').length; }); - res_json.duration = this.evaluate(function() { return document.querySelector('li.duration em').textContent; }); - - res_json.suites = this.evaluate(function() { - var traverse_node = function(elem) { - var res; - if (elem.classList.contains('suite')) { - res = { - type: 'suite', - name: elem.querySelector('h1').textContent, - has_subfailures: elem.querySelectorAll('li.test.fail').length > 0, - }; - - var child_elems = elem.querySelector('ul').children; - res.children = Array.prototype.map.call(child_elems, traverse_node); - return res; - } - else { - var h2_content = elem.querySelector('h2').childNodes; - res = { - type: 'test', - text: h2_content[0].textContent, - }; - - if (elem.classList.contains('pass')) { - res.pass = true; - if (elem.classList.contains('pending')) { - res.slow = false; - res.skipped = true; - } - else { - res.slow = !elem.classList.contains('fast'); - res.skipped = false; - res.duration = h2_content[1].textContent; - } - } - else { - res.error = elem.querySelector('pre.error').textContent; - } - - return res; - } - }; - var top_suites = document.querySelectorAll('#mocha-report > li.suite'); - return Array.prototype.map.call(top_suites, traverse_node); - }); - - res_json.replay = this.evaluate(function() { return document.querySelector('a.replay').textContent; }); - - this.emit('test_ready', res_json); - }]); - }); - spooky.run(); - }); - - return spooky; -}; - -module.exports = { - provide_emitter: provide_emitter, - name: 'SpookyJS (CapserJS on PhantomJS)' -}; diff --git a/tests/run_from_console.js b/tests/run_from_console.js deleted file mode 100755 index a743d47a..00000000 --- a/tests/run_from_console.js +++ /dev/null @@ -1,360 +0,0 @@ -#!/usr/bin/env node -var ansi = require('ansi'); -var program = require('commander'); -var path = require('path'); -var fs = require('fs'); - -var make_list = function(val) { - return val.split(','); -}; - -program - .option('-t, --tests ', 'Run the specified html-file-based test(s). \'testlist\' should be a comma-separated list', make_list, []) - .option('-a, --print-all', 'Print all tests, not just the failures') - .option('--disable-color', 'Explicitly disable color') - .option('-c, --color', 'Explicitly enable color (default is to use color when not outputting to a pipe)') - .option('-i, --auto-inject ', 'Treat the test list as a set of mocha JS files, and automatically generate HTML files with which to test test. \'includefiles\' should be a comma-separated list of paths to javascript files to include in each of the generated HTML files', make_list, null) - .option('-p, --provider ', 'Use the given provider (defaults to "casper"). Currently, may be "casper" or "zombie"', 'casper') - .option('-g, --generate-html', 'Instead of running the tests, just return the path to the generated HTML file, then wait for user interaction to exit (should be used with .js tests).') - .option('-o, --open-in-browser', 'Open the generated HTML files in a web browser using the "open" module (must be used with the "-g"/"--generate-html" option).') - .option('--output-html', 'Instead of running the tests, just output the generated HTML source to STDOUT (should be used with .js tests)') - .option('-d, --debug', 'Show debug output (the "console" event) from the provider') - .option('-r, --relative', 'Use relative paths in the generated HTML file') - .option('--debugger ', 'Enable the remote debugger for CasperJS') - .parse(process.argv); - -if (program.tests.length === 0) { - program.tests = fs.readdirSync(__dirname).filter(function(f) { return (/^test\.(\w|\.|-)+\.js$/).test(f); }); - program.tests = program.tests.map(function (f) { return path.resolve(__dirname, f); }); // add full paths in - console.log('using files %s', program.tests); -} - -var file_paths = []; - -var all_js = program.tests.reduce(function(a,e) { return a && e.slice(-3) == '.js'; }, true); - -var get_path = function (/* arguments */) { - if (program.relative) { - return path.join.apply(null, arguments); - } else { - var args = Array.prototype.slice.call(arguments); - args.unshift(__dirname, '..'); - return path.resolve.apply(null, args); - } -}; - -var get_path_cwd = function (/* arguments */) { - if (program.relative) { - var part_path = path.join.apply(null, arguments); - return path.relative(path.join(__dirname, '..'), path.resolve(process.cwd(), part_path)); - } else { - var args = Array.prototype.slice.call(arguments); - args.unshift(process.cwd()); - return path.resolve.apply(null, args); - } -}; - -if (all_js && !program.autoInject) { - var all_modules = {}; - - // uses the first instance of the string 'requires local modules: ' - program.tests.forEach(function (testname) { - var full_path = path.resolve(process.cwd(), testname); - var content = fs.readFileSync(full_path).toString(); - var ind = content.indexOf('requires local modules: '); - if (ind > -1) { - ind += 'requires local modules: '.length; - var eol = content.indexOf('\n', ind); - var modules = content.slice(ind, eol).split(/,\s*/); - modules.forEach(function (mod) { - all_modules[get_path('core/', mod) + '.js'] = 1; - }); - } - - var fakes_ind = content.indexOf('requires test modules: '); - if (fakes_ind > -1) { - fakes_ind += 'requires test modules: '.length; - var fakes_eol = content.indexOf('\n', fakes_ind); - var fakes_modules = content.slice(fakes_ind, fakes_eol).split(/,\s*/); - fakes_modules.forEach(function (mod) { - all_modules[get_path('tests/', mod) + '.js'] = 1; - }); - } - }); - - program.autoInject = Object.keys(all_modules); -} - -if (program.autoInject) { - var temp = require('temp'); - temp.track(); - - var template = { - header: "\n\n\n\n\n
", - script_tag: function(p) { return ""; }, - footer: "\n\n" - }; - - template.header += "\n" + template.script_tag(get_path('node_modules/chai/chai.js')); - template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js')); - template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js')); - template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js')); - template.header += "\n"; - - - template.header = program.autoInject.reduce(function(acc, sn) { - return acc + "\n" + template.script_tag(get_path_cwd(sn)); - }, template.header); - - file_paths = program.tests.map(function(jsn, ind) { - var templ = template.header; - templ += "\n"; - templ += template.script_tag(get_path_cwd(jsn)); - templ += template.footer; - - var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' }); - fs.writeSync(tempfile.fd, templ); - fs.closeSync(tempfile.fd); - return tempfile.path; - }); - -} -else { - file_paths = program.tests.map(function(fn) { - return path.resolve(process.cwd(), fn); - }); -} - -var use_ansi = false; -if (program.color) use_ansi = true; -else if (program.disableColor) use_ansi = false; -else if (process.stdout.isTTY) use_ansi = true; - -var cursor = ansi(process.stdout, { enabled: use_ansi }); - -if (program.outputHtml) { - file_paths.forEach(function(path, path_ind) { - fs.readFile(path, function(err, data) { - if (err) { - console.warn(error.stack); - return; - } - - if (use_ansi) { - cursor - .bold() - .write(program.tests[path_ind]) - .reset() - .write("\n") - .write(Array(program.tests[path_ind].length+1).join('=')) - .write("\n\n"); - } - - cursor - .write(data) - .write("\n\n"); - }); - }); -} - -if (program.generateHtml) { - var open_browser; - if (program.openInBrowser) { - open_browser = require('open'); - } - - file_paths.forEach(function(path, path_ind) { - cursor - .bold() - .write(program.tests[path_ind]) - .write(": ") - .reset() - .write(path) - .write("\n"); - - if (program.openInBrowser) { - open_browser(path); - } - }); - console.log(''); -} - -if (program.generateHtml) { - process.stdin.resume(); // pause until C-c - process.on('SIGINT', function() { - process.stdin.pause(); // exit - }); -} - -if (!program.outputHtml && !program.generateHtml) { - var failure_count = 0; - - var prov = require(path.resolve(__dirname, 'run_from_console.'+program.provider+'.js')); - - cursor - .write("Running tests ") - .bold() - .write(program.tests.join(', ')) - .reset() - .grey() - .write(' using provider '+prov.name) - .reset() - .write("\n"); - //console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name); - - var provider = prov.provide_emitter(file_paths, program.debugger); - provider.on('test_ready', function(test_json) { - console.log(''); - - filename = program.tests[test_json.file_ind]; - - cursor.bold(); - console.log('Results for %s:', filename); - console.log(Array('Results for :'.length+filename.length+1).join('=')); - cursor.reset(); - - console.log(''); - - cursor - .write(''+test_json.num_tests+' tests run, ') - .green() - .write(''+test_json.num_passes+' passed'); - if (test_json.num_slow > 0) { - cursor - .reset() - .write(' ('); - cursor - .yellow() - .write(''+test_json.num_slow+' slow') - .reset() - .write(')'); - } - cursor - .reset() - .write(', '); - cursor - .red() - .write(''+test_json.num_fails+' failed'); - if (test_json.num_skipped > 0) { - cursor - .reset() - .write(', ') - .grey() - .write(''+test_json.num_skipped+' skipped'); - } - cursor - .reset() - .write(' -- duration: '+test_json.duration+"s\n"); - - console.log(''); - - if (test_json.num_fails > 0 || program.printAll) { - var extract_error_lines = function (err) { - // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too - var err_lines = err.split('\n'); - if (err_lines.length == 1) { - return err_lines[0]; - } else { - var ind; - for (ind = 0; ind < err_lines.length; ind++) { - var at_ind = err_lines[ind].trim().indexOf('at '); - if (at_ind === 0) { - break; - } - } - - return err_lines.slice(0, ind).join('\n'); - } - }; - - var traverse_tree = function(indentation, node) { - if (node.type == 'suite') { - if (!node.has_subfailures && !program.printAll) return; - - if (indentation === 0) { - cursor.bold(); - console.log(node.name); - console.log(Array(node.name.length+1).join('-')); - cursor.reset(); - } - else { - cursor - .write(Array(indentation+3).join('#')) - .bold() - .write(' '+node.name+' ') - .reset() - .write(Array(indentation+3).join('#')) - .write("\n"); - } - - console.log(''); - - for (var i = 0; i < node.children.length; i++) { - traverse_tree(indentation+1, node.children[i]); - } - } - else { - if (!node.pass) { - cursor.magenta(); - console.log('- failed: '+node.text+test_json.replay); - cursor.red(); - console.log(' '+extract_error_lines(node.error)); - cursor.reset(); - console.log(''); - } - else if (program.printAll) { - if (node.skipped) { - cursor - .grey() - .write('- skipped: '+node.text); - } - else { - if (node.slow) cursor.yellow(); - else cursor.green(); - - cursor - .write('- pass: '+node.text) - .grey() - .write(' ('+node.duration+') '); - } - /*if (node.slow) cursor.yellow(); - else cursor.green();*/ - cursor - //.write(test_json.replay) - .reset() - .write("\n"); - console.log(''); - } - } - }; - - for (var i = 0; i < test_json.suites.length; i++) { - traverse_tree(0, test_json.suites[i]); - } - } - - if (test_json.num_fails === 0) { - cursor.fg.green(); - console.log('all tests passed :-)'); - cursor.reset(); - } - }); - - if (program.debug) { - provider.on('console', function(line) { - // log to stderr - console.error(line); - }); - } - - provider.on('error', function(line) { - // log to stderr - console.error('ERROR: ' + line); - }); - - /*gprom.finally(function(ph) { - ph.exit(); - // exit with a status code that actually gives information - if (program.exitWithFailureCount) process.exit(failure_count); - });*/ -} diff --git a/tests/run_from_console.zombie.js b/tests/run_from_console.zombie.js deleted file mode 100644 index 7280f75d..00000000 --- a/tests/run_from_console.zombie.js +++ /dev/null @@ -1,82 +0,0 @@ -var Browser = require('zombie'); -var path = require('path'); -var EventEmitter = require('events').EventEmitter; -var Q = require('q'); - -var provide_emitter = function(file_paths) { - var emitter = new EventEmitter(); - - file_paths.reduce(function(prom, file_path, path_ind) { - return prom.then(function(browser) { - browser.visit('file://'+file_path, function() { - if (browser.error) throw new Error(browser.errors); - - var res_json = {}; - res_json.file_ind = path_ind; - - res_json.num_tests = browser.querySelectorAll('li.test').length; - res_json.num_fails = browser.querySelectorAll('li.test.fail').length; - res_json.num_passes = browser.querySelectorAll('li.test.pass').length; - res_json.num_slow = browser.querySelectorAll('li.test.pass:not(.fast)').length; - res_json.num_skipped = browser.querySelectorAll('li.test.pending').length; - res_json.duration = browser.text('li.duration em'); - - var traverse_node = function(elem) { - var classList = elem.className.split(' '); - var res; - if (classList.indexOf('suite') > -1) { - res = { - type: 'suite', - name: elem.querySelector('h1').textContent, - has_subfailures: elem.querySelectorAll('li.test.fail').length > 0 - }; - - var child_elems = elem.querySelector('ul').children; - res.children = Array.prototype.map.call(child_elems, traverse_node); - return res; - } - else { - var h2_content = elem.querySelector('h2').childNodes; - res = { - type: 'test', - text: h2_content[0].textContent - }; - - if (classList.indexOf('pass') > -1) { - res.pass = true; - if (classList.indexOf('pending') > -1) { - res.slow = false; - res.skipped = true; - } - else { - res.slow = classList.indexOf('fast') < 0; - res.skipped = false; - res.duration = h2_content[1].textContent; - } - } - else { - res.error = elem.querySelector('pre.error').textContent; - } - - return res; - } - }; - - var top_suites = browser.querySelectorAll('#mocha-report > li.suite'); - res_json.suites = Array.prototype.map.call(top_suites, traverse_node); - res_json.replay = browser.querySelector('a.replay').textContent; - - emitter.emit('test_ready', res_json); - }); - - return new Browser(); - }); - }, Q(new Browser())); - - return emitter; -}; - -module.exports = { - provide_emitter: provide_emitter, - name: 'ZombieJS' -}; diff --git a/tests/test.base64.js b/tests/test.base64.js index b2646a0f..7aae7eff 100644 --- a/tests/test.base64.js +++ b/tests/test.base64.js @@ -1,7 +1,8 @@ -// requires local modules: base64 var assert = chai.assert; var expect = chai.expect; +import Base64 from '../core/base64.js'; + describe('Base64 Tools', function() { "use strict"; diff --git a/tests/test.display.js b/tests/test.display.js index e4e33489..4eb771ec 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -1,8 +1,15 @@ -// requires local modules: util, base64, display -// requires test modules: assertions /* jshint expr: true */ var expect = chai.expect; +import Base64 from '../core/base64.js'; +import Display from '../core/display.js'; +import { _forceCursorURIs, browserSupportsCursorURIs } from '../core/util/browsers.js'; + +import './assertions.js'; +import 'sinon'; +import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js' +chai.use(sinonChai); + describe('Display/Canvas Helper', function () { var checked_data = [ 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, @@ -34,31 +41,23 @@ describe('Display/Canvas Helper', function () { } describe('checking for cursor uri support', function () { - beforeEach(function () { - this._old_browser_supports_cursor_uris = Util.browserSupportsCursorURIs; - }); - it('should disable cursor URIs if there is no support', function () { - Util.browserSupportsCursorURIs = function () { return false; }; + _forceCursorURIs(false); var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); expect(display._cursor_uri).to.be.false; }); it('should enable cursor URIs if there is support', function () { - Util.browserSupportsCursorURIs = function () { return true; }; + _forceCursorURIs(true); var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); expect(display._cursor_uri).to.be.true; }); it('respect the cursor_uri option if there is support', function () { - Util.browserSupportsCursorURIs = function () { return false; }; + _forceCursorURIs(false); var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false }); expect(display._cursor_uri).to.be.false; }); - - afterEach(function () { - Util.browserSupportsCursorURIs = this._old_browser_supports_cursor_uris; - }); }); describe('viewport handling', function () { diff --git a/tests/test.helper.js b/tests/test.helper.js index 4a1c65f3..12232d82 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -1,8 +1,9 @@ -// requires local modules: input/keysym, input/keysymdef, input/util - -var assert = chai.assert; +var assert = chai.assert; var expect = chai.expect; +import keysyms from '../core/input/keysymdef.js'; +import * as KeyboardUtil from "../core/input/util.js"; + describe('Helpers', function() { "use strict"; describe('keysymFromKeyCode', function() { diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 0019be9a..b68c96d9 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -1,7 +1,9 @@ -// requires local modules: input/devices, input/util, input/keysymdef, input/keysym var assert = chai.assert; var expect = chai.expect; +import keysyms from '../core/input/keysymdef.js'; +import * as KeyboardUtil from '../core/input/util.js'; + /* jshint newcap: false, expr: true */ describe('Key Event Pipeline Stages', function() { "use strict"; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index f903d052..dd658e04 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1,9 +1,16 @@ -// requires local modules: util, websock, rfb, input/util, input/keysym, input/keysymdef, input/devices, inflator, des, display -// requires test modules: fake.websocket, assertions /* jshint expr: true */ var assert = chai.assert; var expect = chai.expect; +import RFB from '../core/rfb.js'; +import Websock from '../core/websock.js'; + +import FakeWebSocket from './fake.websocket.js'; +import './assertions'; +import 'sinon'; +import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js' +chai.use(sinonChai); + function make_rfb (extra_opts) { if (!extra_opts) { extra_opts = {}; diff --git a/tests/test.util.js b/tests/test.util.js index e6a79bb6..8bd93765 100644 --- a/tests/test.util.js +++ b/tests/test.util.js @@ -1,9 +1,15 @@ -// requires local modules: util /* jshint expr: true */ var assert = chai.assert; var expect = chai.expect; +import * as Log from '../core/util/logging.js'; +import l10nGet, { l10n } from '../core/util/localization.js'; + +import 'sinon'; +import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js' +chai.use(sinonChai); + describe('Utils', function() { "use strict"; @@ -25,34 +31,34 @@ describe('Utils', function() { }); it('should use noop for levels lower than the min level', function () { - Util.init_logging('warn'); - Util.Debug('hi'); - Util.Info('hello'); + Log.init_logging('warn'); + Log.Debug('hi'); + Log.Info('hello'); expect(console.log).to.not.have.been.called; }); it('should use console.debug for Debug', function () { - Util.init_logging('debug'); - Util.Debug('dbg'); + Log.init_logging('debug'); + Log.Debug('dbg'); expect(console.debug).to.have.been.calledWith('dbg'); }); it('should use console.info for Info', function () { - Util.init_logging('debug'); - Util.Info('inf'); + Log.init_logging('debug'); + Log.Info('inf'); expect(console.info).to.have.been.calledWith('inf'); }); it('should use console.warn for Warn', function () { - Util.init_logging('warn'); - Util.Warn('wrn'); + Log.init_logging('warn'); + Log.Warn('wrn'); expect(console.warn).to.have.been.called; expect(console.warn).to.have.been.calledWith('wrn'); }); it('should use console.error for Error', function () { - Util.init_logging('error'); - Util.Error('err'); + Log.init_logging('error'); + Log.Error('err'); expect(console.error).to.have.been.called; expect(console.error).to.have.been.calledWith('err'); }); @@ -85,42 +91,42 @@ describe('Utils', function() { }); it('should use English by default', function() { - expect(Util.Localisation.language).to.equal('en'); + expect(l10n.language).to.equal('en'); }); it('should use English if no user language matches', function() { window.navigator.languages = ["nl", "de"]; - Util.Localisation.setup(["es", "fr"]); - expect(Util.Localisation.language).to.equal('en'); + l10n.setup(["es", "fr"]); + expect(l10n.language).to.equal('en'); }); it('should use the most preferred user language', function() { window.navigator.languages = ["nl", "de", "fr"]; - Util.Localisation.setup(["es", "fr", "de"]); - expect(Util.Localisation.language).to.equal('de'); + l10n.setup(["es", "fr", "de"]); + expect(l10n.language).to.equal('de'); }); it('should prefer sub-languages languages', function() { window.navigator.languages = ["pt-BR"]; - Util.Localisation.setup(["pt", "pt-BR"]); - expect(Util.Localisation.language).to.equal('pt-BR'); + l10n.setup(["pt", "pt-BR"]); + expect(l10n.language).to.equal('pt-BR'); }); it('should fall back to language "parents"', function() { window.navigator.languages = ["pt-BR"]; - Util.Localisation.setup(["fr", "pt", "de"]); - expect(Util.Localisation.language).to.equal('pt'); + l10n.setup(["fr", "pt", "de"]); + expect(l10n.language).to.equal('pt'); }); it('should not use specific language when user asks for a generic language', function() { window.navigator.languages = ["pt", "de"]; - Util.Localisation.setup(["fr", "pt-BR", "de"]); - expect(Util.Localisation.language).to.equal('de'); + l10n.setup(["fr", "pt-BR", "de"]); + expect(l10n.language).to.equal('de'); }); it('should handle underscore as a separator', function() { window.navigator.languages = ["pt-BR"]; - Util.Localisation.setup(["pt_BR"]); - expect(Util.Localisation.language).to.equal('pt_BR'); + l10n.setup(["pt_BR"]); + expect(l10n.language).to.equal('pt_BR'); }); it('should handle difference in case', function() { window.navigator.languages = ["pt-br"]; - Util.Localisation.setup(["pt-BR"]); - expect(Util.Localisation.language).to.equal('pt-BR'); + l10n.setup(["pt-BR"]); + expect(l10n.language).to.equal('pt-BR'); }); }); diff --git a/tests/test.websock.js b/tests/test.websock.js index 023c7726..b4835901 100644 --- a/tests/test.websock.js +++ b/tests/test.websock.js @@ -1,9 +1,15 @@ -// requires local modules: websock, util -// requires test modules: fake.websocket, assertions /* jshint expr: true */ var assert = chai.assert; var expect = chai.expect; +import Websock from '../core/websock.js'; +import FakeWebSocket from './fake.websocket.js'; + +import './assertions'; +import 'sinon'; +import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js' +chai.use(sinonChai); + describe('Websock', function() { "use strict"; diff --git a/vendor/sinon.js b/vendor/sinon.js new file mode 100644 index 00000000..17e58860 --- /dev/null +++ b/vendor/sinon.js @@ -0,0 +1,6463 @@ +/** + * Sinon.JS 1.17.6, 2016/09/19 + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS + * + * (The BSD License) + * + * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Christian Johansen nor the names of his contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function (root, factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + define('sinon', [], function () { + return (root.sinon = factory()); + }); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.sinon = factory(); + } +}(this, function () { + 'use strict'; + var samsam, formatio, lolex; + (function () { + function define(mod, deps, fn) { + if (mod == "samsam") { + samsam = deps(); + } else if (typeof deps === "function" && mod.length === 0) { + lolex = deps(); + } else if (typeof fn === "function") { + formatio = fn(samsam); + } + } + define.amd = {}; +((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) || + (typeof module === "object" && + function (m) { module.exports = m(); }) || // Node + function (m) { this.samsam = m(); } // Browser globals +)(function () { + var o = Object.prototype; + var div = typeof document !== "undefined" && document.createElement("div"); + + function isNaN(value) { + // Unlike global isNaN, this avoids type coercion + // typeof check avoids IE host object issues, hat tip to + // lodash + var val = value; // JsLint thinks value !== value is "weird" + return typeof value === "number" && value !== val; + } + + function getClass(value) { + // Returns the internal [[Class]] by calling Object.prototype.toString + // with the provided value as this. Return value is a string, naming the + // internal class, e.g. "Array" + return o.toString.call(value).split(/[ \]]/)[1]; + } + + /** + * @name samsam.isArguments + * @param Object object + * + * Returns ``true`` if ``object`` is an ``arguments`` object, + * ``false`` otherwise. + */ + function isArguments(object) { + if (getClass(object) === 'Arguments') { return true; } + if (typeof object !== "object" || typeof object.length !== "number" || + getClass(object) === "Array") { + return false; + } + if (typeof object.callee == "function") { return true; } + try { + object[object.length] = 6; + delete object[object.length]; + } catch (e) { + return true; + } + return false; + } + + /** + * @name samsam.isElement + * @param Object object + * + * Returns ``true`` if ``object`` is a DOM element node. Unlike + * Underscore.js/lodash, this function will return ``false`` if ``object`` + * is an *element-like* object, i.e. a regular object with a ``nodeType`` + * property that holds the value ``1``. + */ + function isElement(object) { + if (!object || object.nodeType !== 1 || !div) { return false; } + try { + object.appendChild(div); + object.removeChild(div); + } catch (e) { + return false; + } + return true; + } + + /** + * @name samsam.keys + * @param Object object + * + * Return an array of own property names. + */ + function keys(object) { + var ks = [], prop; + for (prop in object) { + if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); } + } + return ks; + } + + /** + * @name samsam.isDate + * @param Object value + * + * Returns true if the object is a ``Date``, or *date-like*. Duck typing + * of date objects work by checking that the object has a ``getTime`` + * function whose return value equals the return value from the object's + * ``valueOf``. + */ + function isDate(value) { + return typeof value.getTime == "function" && + value.getTime() == value.valueOf(); + } + + /** + * @name samsam.isNegZero + * @param Object value + * + * Returns ``true`` if ``value`` is ``-0``. + */ + function isNegZero(value) { + return value === 0 && 1 / value === -Infinity; + } + + /** + * @name samsam.equal + * @param Object obj1 + * @param Object obj2 + * + * Returns ``true`` if two objects are strictly equal. Compared to + * ``===`` there are two exceptions: + * + * - NaN is considered equal to NaN + * - -0 and +0 are not considered equal + */ + function identical(obj1, obj2) { + if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) { + return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2); + } + } + + + /** + * @name samsam.deepEqual + * @param Object obj1 + * @param Object obj2 + * + * Deep equal comparison. Two values are "deep equal" if: + * + * - They are equal, according to samsam.identical + * - They are both date objects representing the same time + * - They are both arrays containing elements that are all deepEqual + * - They are objects with the same set of properties, and each property + * in ``obj1`` is deepEqual to the corresponding property in ``obj2`` + * + * Supports cyclic objects. + */ + function deepEqualCyclic(obj1, obj2) { + + // used for cyclic comparison + // contain already visited objects + var objects1 = [], + objects2 = [], + // contain pathes (position in the object structure) + // of the already visited objects + // indexes same as in objects arrays + paths1 = [], + paths2 = [], + // contains combinations of already compared objects + // in the manner: { "$1['ref']$2['ref']": true } + compared = {}; + + /** + * used to check, if the value of a property is an object + * (cyclic logic is only needed for objects) + * only needed for cyclic logic + */ + function isObject(value) { + + if (typeof value === 'object' && value !== null && + !(value instanceof Boolean) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && + !(value instanceof String)) { + + return true; + } + + return false; + } + + /** + * returns the index of the given object in the + * given objects array, -1 if not contained + * only needed for cyclic logic + */ + function getIndex(objects, obj) { + + var i; + for (i = 0; i < objects.length; i++) { + if (objects[i] === obj) { + return i; + } + } + + return -1; + } + + // does the recursion for the deep equal check + return (function deepEqual(obj1, obj2, path1, path2) { + var type1 = typeof obj1; + var type2 = typeof obj2; + + // == null also matches undefined + if (obj1 === obj2 || + isNaN(obj1) || isNaN(obj2) || + obj1 == null || obj2 == null || + type1 !== "object" || type2 !== "object") { + + return identical(obj1, obj2); + } + + // Elements are only equal if identical(expected, actual) + if (isElement(obj1) || isElement(obj2)) { return false; } + + var isDate1 = isDate(obj1), isDate2 = isDate(obj2); + if (isDate1 || isDate2) { + if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) { + return false; + } + } + + if (obj1 instanceof RegExp && obj2 instanceof RegExp) { + if (obj1.toString() !== obj2.toString()) { return false; } + } + + var class1 = getClass(obj1); + var class2 = getClass(obj2); + var keys1 = keys(obj1); + var keys2 = keys(obj2); + + if (isArguments(obj1) || isArguments(obj2)) { + if (obj1.length !== obj2.length) { return false; } + } else { + if (type1 !== type2 || class1 !== class2 || + keys1.length !== keys2.length) { + return false; + } + } + + var key, i, l, + // following vars are used for the cyclic logic + value1, value2, + isObject1, isObject2, + index1, index2, + newPath1, newPath2; + + for (i = 0, l = keys1.length; i < l; i++) { + key = keys1[i]; + if (!o.hasOwnProperty.call(obj2, key)) { + return false; + } + + // Start of the cyclic logic + + value1 = obj1[key]; + value2 = obj2[key]; + + isObject1 = isObject(value1); + isObject2 = isObject(value2); + + // determine, if the objects were already visited + // (it's faster to check for isObject first, than to + // get -1 from getIndex for non objects) + index1 = isObject1 ? getIndex(objects1, value1) : -1; + index2 = isObject2 ? getIndex(objects2, value2) : -1; + + // determine the new pathes of the objects + // - for non cyclic objects the current path will be extended + // by current property name + // - for cyclic objects the stored path is taken + newPath1 = index1 !== -1 + ? paths1[index1] + : path1 + '[' + JSON.stringify(key) + ']'; + newPath2 = index2 !== -1 + ? paths2[index2] + : path2 + '[' + JSON.stringify(key) + ']'; + + // stop recursion if current objects are already compared + if (compared[newPath1 + newPath2]) { + return true; + } + + // remember the current objects and their pathes + if (index1 === -1 && isObject1) { + objects1.push(value1); + paths1.push(newPath1); + } + if (index2 === -1 && isObject2) { + objects2.push(value2); + paths2.push(newPath2); + } + + // remember that the current objects are already compared + if (isObject1 && isObject2) { + compared[newPath1 + newPath2] = true; + } + + // End of cyclic logic + + // neither value1 nor value2 is a cycle + // continue with next level + if (!deepEqual(value1, value2, newPath1, newPath2)) { + return false; + } + } + + return true; + + }(obj1, obj2, '$1', '$2')); + } + + var match; + + function arrayContains(array, subset) { + if (subset.length === 0) { return true; } + var i, l, j, k; + for (i = 0, l = array.length; i < l; ++i) { + if (match(array[i], subset[0])) { + for (j = 0, k = subset.length; j < k; ++j) { + if (!match(array[i + j], subset[j])) { return false; } + } + return true; + } + } + return false; + } + + /** + * @name samsam.match + * @param Object object + * @param Object matcher + * + * Compare arbitrary value ``object`` with matcher. + */ + match = function match(object, matcher) { + if (matcher && typeof matcher.test === "function") { + return matcher.test(object); + } + + if (typeof matcher === "function") { + return matcher(object) === true; + } + + if (typeof matcher === "string") { + matcher = matcher.toLowerCase(); + var notNull = typeof object === "string" || !!object; + return notNull && + (String(object)).toLowerCase().indexOf(matcher) >= 0; + } + + if (typeof matcher === "number") { + return matcher === object; + } + + if (typeof matcher === "boolean") { + return matcher === object; + } + + if (typeof(matcher) === "undefined") { + return typeof(object) === "undefined"; + } + + if (matcher === null) { + return object === null; + } + + if (getClass(object) === "Array" && getClass(matcher) === "Array") { + return arrayContains(object, matcher); + } + + if (matcher && typeof matcher === "object") { + if (matcher === object) { + return true; + } + var prop; + for (prop in matcher) { + var value = object[prop]; + if (typeof value === "undefined" && + typeof object.getAttribute === "function") { + value = object.getAttribute(prop); + } + if (matcher[prop] === null || typeof matcher[prop] === 'undefined') { + if (value !== matcher[prop]) { + return false; + } + } else if (typeof value === "undefined" || !match(value, matcher[prop])) { + return false; + } + } + return true; + } + + throw new Error("Matcher was not a string, a number, a " + + "function, a boolean or an object"); + }; + + return { + isArguments: isArguments, + isElement: isElement, + isDate: isDate, + isNegZero: isNegZero, + identical: identical, + deepEqual: deepEqualCyclic, + match: match, + keys: keys + }; +}); +((typeof define === "function" && define.amd && function (m) { + define("formatio", ["samsam"], m); +}) || (typeof module === "object" && function (m) { + module.exports = m(require("samsam")); +}) || function (m) { this.formatio = m(this.samsam); } +)(function (samsam) { + + var formatio = { + excludeConstructors: ["Object", /^.$/], + quoteStrings: true, + limitChildrenCount: 0 + }; + + var hasOwn = Object.prototype.hasOwnProperty; + + var specialObjects = []; + if (typeof global !== "undefined") { + specialObjects.push({ object: global, value: "[object global]" }); + } + if (typeof document !== "undefined") { + specialObjects.push({ + object: document, + value: "[object HTMLDocument]" + }); + } + if (typeof window !== "undefined") { + specialObjects.push({ object: window, value: "[object Window]" }); + } + + function functionName(func) { + if (!func) { return ""; } + if (func.displayName) { return func.displayName; } + if (func.name) { return func.name; } + var matches = func.toString().match(/function\s+([^\(]+)/m); + return (matches && matches[1]) || ""; + } + + function constructorName(f, object) { + var name = functionName(object && object.constructor); + var excludes = f.excludeConstructors || + formatio.excludeConstructors || []; + + var i, l; + for (i = 0, l = excludes.length; i < l; ++i) { + if (typeof excludes[i] === "string" && excludes[i] === name) { + return ""; + } else if (excludes[i].test && excludes[i].test(name)) { + return ""; + } + } + + return name; + } + + function isCircular(object, objects) { + if (typeof object !== "object") { return false; } + var i, l; + for (i = 0, l = objects.length; i < l; ++i) { + if (objects[i] === object) { return true; } + } + return false; + } + + function ascii(f, object, processed, indent) { + if (typeof object === "string") { + var qs = f.quoteStrings; + var quote = typeof qs !== "boolean" || qs; + return processed || quote ? '"' + object + '"' : object; + } + + if (typeof object === "function" && !(object instanceof RegExp)) { + return ascii.func(object); + } + + processed = processed || []; + + if (isCircular(object, processed)) { return "[Circular]"; } + + if (Object.prototype.toString.call(object) === "[object Array]") { + return ascii.array.call(f, object, processed); + } + + if (!object) { return String((1/object) === -Infinity ? "-0" : object); } + if (samsam.isElement(object)) { return ascii.element(object); } + + if (typeof object.toString === "function" && + object.toString !== Object.prototype.toString) { + return object.toString(); + } + + var i, l; + for (i = 0, l = specialObjects.length; i < l; i++) { + if (object === specialObjects[i].object) { + return specialObjects[i].value; + } + } + + return ascii.object.call(f, object, processed, indent); + } + + ascii.func = function (func) { + return "function " + functionName(func) + "() {}"; + }; + + ascii.array = function (array, processed) { + processed = processed || []; + processed.push(array); + var pieces = []; + var i, l; + l = (this.limitChildrenCount > 0) ? + Math.min(this.limitChildrenCount, array.length) : array.length; + + for (i = 0; i < l; ++i) { + pieces.push(ascii(this, array[i], processed)); + } + + if(l < array.length) + pieces.push("[... " + (array.length - l) + " more elements]"); + + return "[" + pieces.join(", ") + "]"; + }; + + ascii.object = function (object, processed, indent) { + processed = processed || []; + processed.push(object); + indent = indent || 0; + var pieces = [], properties = samsam.keys(object).sort(); + var length = 3; + var prop, str, obj, i, k, l; + l = (this.limitChildrenCount > 0) ? + Math.min(this.limitChildrenCount, properties.length) : properties.length; + + for (i = 0; i < l; ++i) { + prop = properties[i]; + obj = object[prop]; + + if (isCircular(obj, processed)) { + str = "[Circular]"; + } else { + str = ascii(this, obj, processed, indent + 2); + } + + str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; + length += str.length; + pieces.push(str); + } + + var cons = constructorName(this, object); + var prefix = cons ? "[" + cons + "] " : ""; + var is = ""; + for (i = 0, k = indent; i < k; ++i) { is += " "; } + + if(l < properties.length) + pieces.push("[... " + (properties.length - l) + " more elements]"); + + if (length + indent > 80) { + return prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + + is + "}"; + } + return prefix + "{ " + pieces.join(", ") + " }"; + }; + + ascii.element = function (element) { + var tagName = element.tagName.toLowerCase(); + var attrs = element.attributes, attr, pairs = [], attrName, i, l, val; + + for (i = 0, l = attrs.length; i < l; ++i) { + attr = attrs.item(i); + attrName = attr.nodeName.toLowerCase().replace("html:", ""); + val = attr.nodeValue; + if (attrName !== "contenteditable" || val !== "inherit") { + if (!!val) { pairs.push(attrName + "=\"" + val + "\""); } + } + } + + var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); + var content = element.innerHTML; + + if (content.length > 20) { + content = content.substr(0, 20) + "[...]"; + } + + var res = formatted + pairs.join(" ") + ">" + content + + ""; + + return res.replace(/ contentEditable="inherit"/, ""); + }; + + function Formatio(options) { + for (var opt in options) { + this[opt] = options[opt]; + } + } + + Formatio.prototype = { + functionName: functionName, + + configure: function (options) { + return new Formatio(options); + }, + + constructorName: function (object) { + return constructorName(this, object); + }, + + ascii: function (object, processed, indent) { + return ascii(this, object, processed, indent); + } + }; + + return Formatio.prototype; +}); +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.lolex=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { + throw new Error("tick only understands numbers and 'h:m:s'"); + } + + while (i--) { + parsed = parseInt(strings[i], 10); + + if (parsed >= 60) { + throw new Error("Invalid time " + str); + } + + ms += parsed * Math.pow(60, (l - i - 1)); + } + + return ms * 1000; + } + + /** + * Used to grok the `now` parameter to createClock. + */ + function getEpoch(epoch) { + if (!epoch) { return 0; } + if (typeof epoch.getTime === "function") { return epoch.getTime(); } + if (typeof epoch === "number") { return epoch; } + throw new TypeError("now should be milliseconds since UNIX epoch"); + } + + function inRange(from, to, timer) { + return timer && timer.callAt >= from && timer.callAt <= to; + } + + function mirrorDateProperties(target, source) { + var prop; + for (prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + + // set special now implementation + if (source.now) { + target.now = function now() { + return target.clock.now; + }; + } else { + delete target.now; + } + + // set special toSource implementation + if (source.toSource) { + target.toSource = function toSource() { + return source.toSource(); + }; + } else { + delete target.toSource; + } + + // set special toString implementation + target.toString = function toString() { + return source.toString(); + }; + + target.prototype = source.prototype; + target.parse = source.parse; + target.UTC = source.UTC; + target.prototype.toUTCString = source.prototype.toUTCString; + + return target; + } + + function createDate() { + function ClockDate(year, month, date, hour, minute, second, ms) { + // Defensive and verbose to avoid potential harm in passing + // explicit undefined when user does not pass argument + switch (arguments.length) { + case 0: + return new NativeDate(ClockDate.clock.now); + case 1: + return new NativeDate(year); + case 2: + return new NativeDate(year, month); + case 3: + return new NativeDate(year, month, date); + case 4: + return new NativeDate(year, month, date, hour); + case 5: + return new NativeDate(year, month, date, hour, minute); + case 6: + return new NativeDate(year, month, date, hour, minute, second); + default: + return new NativeDate(year, month, date, hour, minute, second, ms); + } + } + + return mirrorDateProperties(ClockDate, NativeDate); + } + + function addTimer(clock, timer) { + if (timer.func === undefined) { + throw new Error("Callback must be provided to timer calls"); + } + + if (!clock.timers) { + clock.timers = {}; + } + + timer.id = uniqueTimerId++; + timer.createdAt = clock.now; + timer.callAt = clock.now + (timer.delay || (clock.duringTick ? 1 : 0)); + + clock.timers[timer.id] = timer; + + if (addTimerReturnsObject) { + return { + id: timer.id, + ref: NOOP, + unref: NOOP + }; + } + + return timer.id; + } + + + function compareTimers(a, b) { + // Sort first by absolute timing + if (a.callAt < b.callAt) { + return -1; + } + if (a.callAt > b.callAt) { + return 1; + } + + // Sort next by immediate, immediate timers take precedence + if (a.immediate && !b.immediate) { + return -1; + } + if (!a.immediate && b.immediate) { + return 1; + } + + // Sort next by creation time, earlier-created timers take precedence + if (a.createdAt < b.createdAt) { + return -1; + } + if (a.createdAt > b.createdAt) { + return 1; + } + + // Sort next by id, lower-id timers take precedence + if (a.id < b.id) { + return -1; + } + if (a.id > b.id) { + return 1; + } + + // As timer ids are unique, no fallback `0` is necessary + } + + function firstTimerInRange(clock, from, to) { + var timers = clock.timers, + timer = null, + id, + isInRange; + + for (id in timers) { + if (timers.hasOwnProperty(id)) { + isInRange = inRange(from, to, timers[id]); + + if (isInRange && (!timer || compareTimers(timer, timers[id]) === 1)) { + timer = timers[id]; + } + } + } + + return timer; + } + + function callTimer(clock, timer) { + var exception; + + if (typeof timer.interval === "number") { + clock.timers[timer.id].callAt += timer.interval; + } else { + delete clock.timers[timer.id]; + } + + try { + if (typeof timer.func === "function") { + timer.func.apply(null, timer.args); + } else { + eval(timer.func); + } + } catch (e) { + exception = e; + } + + if (!clock.timers[timer.id]) { + if (exception) { + throw exception; + } + return; + } + + if (exception) { + throw exception; + } + } + + function timerType(timer) { + if (timer.immediate) { + return "Immediate"; + } else if (typeof timer.interval !== "undefined") { + return "Interval"; + } else { + return "Timeout"; + } + } + + function clearTimer(clock, timerId, ttype) { + if (!timerId) { + // null appears to be allowed in most browsers, and appears to be + // relied upon by some libraries, like Bootstrap carousel + return; + } + + if (!clock.timers) { + clock.timers = []; + } + + // in Node, timerId is an object with .ref()/.unref(), and + // its .id field is the actual timer id. + if (typeof timerId === "object") { + timerId = timerId.id; + } + + if (clock.timers.hasOwnProperty(timerId)) { + // check that the ID matches a timer of the correct type + var timer = clock.timers[timerId]; + if (timerType(timer) === ttype) { + delete clock.timers[timerId]; + } else { + throw new Error("Cannot clear timer: timer created with set" + ttype + "() but cleared with clear" + timerType(timer) + "()"); + } + } + } + + function uninstall(clock, target) { + var method, + i, + l; + + for (i = 0, l = clock.methods.length; i < l; i++) { + method = clock.methods[i]; + + if (target[method].hadOwnProperty) { + target[method] = clock["_" + method]; + } else { + try { + delete target[method]; + } catch (ignore) {} + } + } + + // Prevent multiple executions which will completely remove these props + clock.methods = []; + } + + function hijackMethod(target, method, clock) { + var prop; + + clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(target, method); + clock["_" + method] = target[method]; + + if (method === "Date") { + var date = mirrorDateProperties(clock[method], target[method]); + target[method] = date; + } else { + target[method] = function () { + return clock[method].apply(clock, arguments); + }; + + for (prop in clock[method]) { + if (clock[method].hasOwnProperty(prop)) { + target[method][prop] = clock[method][prop]; + } + } + } + + target[method].clock = clock; + } + + var timers = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setImmediate: global.setImmediate, + clearImmediate: global.clearImmediate, + setInterval: setInterval, + clearInterval: clearInterval, + Date: Date + }; + + var keys = Object.keys || function (obj) { + var ks = [], + key; + + for (key in obj) { + if (obj.hasOwnProperty(key)) { + ks.push(key); + } + } + + return ks; + }; + + exports.timers = timers; + + function createClock(now) { + var clock = { + now: getEpoch(now), + timeouts: {}, + Date: createDate() + }; + + clock.Date.clock = clock; + + clock.setTimeout = function setTimeout(func, timeout) { + return addTimer(clock, { + func: func, + args: Array.prototype.slice.call(arguments, 2), + delay: timeout + }); + }; + + clock.clearTimeout = function clearTimeout(timerId) { + return clearTimer(clock, timerId, "Timeout"); + }; + + clock.setInterval = function setInterval(func, timeout) { + return addTimer(clock, { + func: func, + args: Array.prototype.slice.call(arguments, 2), + delay: timeout, + interval: timeout + }); + }; + + clock.clearInterval = function clearInterval(timerId) { + return clearTimer(clock, timerId, "Interval"); + }; + + clock.setImmediate = function setImmediate(func) { + return addTimer(clock, { + func: func, + args: Array.prototype.slice.call(arguments, 1), + immediate: true + }); + }; + + clock.clearImmediate = function clearImmediate(timerId) { + return clearTimer(clock, timerId, "Immediate"); + }; + + clock.tick = function tick(ms) { + ms = typeof ms === "number" ? ms : parseTime(ms); + var tickFrom = clock.now, tickTo = clock.now + ms, previous = clock.now; + var timer = firstTimerInRange(clock, tickFrom, tickTo); + var oldNow; + + clock.duringTick = true; + + var firstException; + while (timer && tickFrom <= tickTo) { + if (clock.timers[timer.id]) { + tickFrom = clock.now = timer.callAt; + try { + oldNow = clock.now; + callTimer(clock, timer); + // compensate for any setSystemTime() call during timer callback + if (oldNow !== clock.now) { + tickFrom += clock.now - oldNow; + tickTo += clock.now - oldNow; + previous += clock.now - oldNow; + } + } catch (e) { + firstException = firstException || e; + } + } + + timer = firstTimerInRange(clock, previous, tickTo); + previous = tickFrom; + } + + clock.duringTick = false; + clock.now = tickTo; + + if (firstException) { + throw firstException; + } + + return clock.now; + }; + + clock.reset = function reset() { + clock.timers = {}; + }; + + clock.setSystemTime = function setSystemTime(now) { + // determine time difference + var newNow = getEpoch(now); + var difference = newNow - clock.now; + + // update 'system clock' + clock.now = newNow; + + // update timers and intervals to keep them stable + for (var id in clock.timers) { + if (clock.timers.hasOwnProperty(id)) { + var timer = clock.timers[id]; + timer.createdAt += difference; + timer.callAt += difference; + } + } + }; + + return clock; + } + exports.createClock = createClock; + + exports.install = function install(target, now, toFake) { + var i, + l; + + if (typeof target === "number") { + toFake = now; + now = target; + target = null; + } + + if (!target) { + target = global; + } + + var clock = createClock(now); + + clock.uninstall = function () { + uninstall(clock, target); + }; + + clock.methods = toFake || []; + + if (clock.methods.length === 0) { + clock.methods = keys(timers); + } + + for (i = 0, l = clock.methods.length; i < l; i++) { + hijackMethod(target, clock.methods[i], clock); + } + + return clock; + }; + +}(global || this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}]},{},[1])(1) +}); + })(); + var define; +/** + * Sinon core utilities. For internal use only. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +var sinon = (function () { +"use strict"; + // eslint-disable-line no-unused-vars + + var sinonModule; + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + sinonModule = module.exports = require("./sinon/util/core"); + require("./sinon/extend"); + require("./sinon/walk"); + require("./sinon/typeOf"); + require("./sinon/times_in_words"); + require("./sinon/spy"); + require("./sinon/call"); + require("./sinon/behavior"); + require("./sinon/stub"); + require("./sinon/mock"); + require("./sinon/collection"); + require("./sinon/assert"); + require("./sinon/sandbox"); + require("./sinon/test"); + require("./sinon/test_case"); + require("./sinon/match"); + require("./sinon/format"); + require("./sinon/log_error"); + } + + if (isAMD) { + define(loadDependencies); + } else if (isNode) { + loadDependencies(require, module.exports, module); + sinonModule = module.exports; + } else { + sinonModule = {}; + } + + return sinonModule; +}()); + +/** + * @depend ../../sinon.js + */ +/** + * Sinon core utilities. For internal use only. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + var div = typeof document !== "undefined" && document.createElement("div"); + var hasOwn = Object.prototype.hasOwnProperty; + + function isDOMNode(obj) { + var success = false; + + try { + obj.appendChild(div); + success = div.parentNode === obj; + } catch (e) { + return false; + } finally { + try { + obj.removeChild(div); + } catch (e) { + // Remove failed, not much we can do about that + } + } + + return success; + } + + function isElement(obj) { + return div && obj && obj.nodeType === 1 && isDOMNode(obj); + } + + function isFunction(obj) { + return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply); + } + + function isReallyNaN(val) { + return typeof val === "number" && isNaN(val); + } + + function mirrorProperties(target, source) { + for (var prop in source) { + if (!hasOwn.call(target, prop)) { + target[prop] = source[prop]; + } + } + } + + function isRestorable(obj) { + return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon; + } + + // Cheap way to detect if we have ES5 support. + var hasES5Support = "keys" in Object; + + function makeApi(sinon) { + sinon.wrapMethod = function wrapMethod(object, property, method) { + if (!object) { + throw new TypeError("Should wrap property of object"); + } + + if (typeof method !== "function" && typeof method !== "object") { + throw new TypeError("Method wrapper should be a function or a property descriptor"); + } + + function checkWrappedMethod(wrappedMethod) { + var error; + + if (!isFunction(wrappedMethod)) { + error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + + property + " as function"); + } else if (wrappedMethod.restore && wrappedMethod.restore.sinon) { + error = new TypeError("Attempted to wrap " + property + " which is already wrapped"); + } else if (wrappedMethod.calledBefore) { + var verb = wrappedMethod.returns ? "stubbed" : "spied on"; + error = new TypeError("Attempted to wrap " + property + " which is already " + verb); + } + + if (error) { + if (wrappedMethod && wrappedMethod.stackTrace) { + error.stack += "\n--------------\n" + wrappedMethod.stackTrace; + } + throw error; + } + } + + var error, wrappedMethod, i; + + function simplePropertyAssignment() { + wrappedMethod = object[property]; + checkWrappedMethod(wrappedMethod); + object[property] = method; + method.displayName = property; + } + + // IE 8 does not support hasOwnProperty on the window object and Firefox has a problem + // when using hasOwn.call on objects from other frames. + var owned = object.hasOwnProperty ? object.hasOwnProperty(property) : hasOwn.call(object, property); + + if (hasES5Support) { + var methodDesc = (typeof method === "function") ? {value: method} : method; + var wrappedMethodDesc = sinon.getPropertyDescriptor(object, property); + + if (!wrappedMethodDesc) { + error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + + property + " as function"); + } else if (wrappedMethodDesc.restore && wrappedMethodDesc.restore.sinon) { + error = new TypeError("Attempted to wrap " + property + " which is already wrapped"); + } + if (error) { + if (wrappedMethodDesc && wrappedMethodDesc.stackTrace) { + error.stack += "\n--------------\n" + wrappedMethodDesc.stackTrace; + } + throw error; + } + + var types = sinon.objectKeys(methodDesc); + for (i = 0; i < types.length; i++) { + wrappedMethod = wrappedMethodDesc[types[i]]; + checkWrappedMethod(wrappedMethod); + } + + mirrorProperties(methodDesc, wrappedMethodDesc); + for (i = 0; i < types.length; i++) { + mirrorProperties(methodDesc[types[i]], wrappedMethodDesc[types[i]]); + } + Object.defineProperty(object, property, methodDesc); + + // catch failing assignment + // this is the converse of the check in `.restore` below + if ( typeof method === "function" && object[property] !== method ) { + // correct any wrongdoings caused by the defineProperty call above, + // such as adding new items (if object was a Storage object) + delete object[property]; + simplePropertyAssignment(); + } + } else { + simplePropertyAssignment(); + } + + method.displayName = property; + + // Set up a stack trace which can be used later to find what line of + // code the original method was created on. + method.stackTrace = (new Error("Stack Trace for original")).stack; + + method.restore = function () { + // For prototype properties try to reset by delete first. + // If this fails (ex: localStorage on mobile safari) then force a reset + // via direct assignment. + if (!owned) { + // In some cases `delete` may throw an error + try { + delete object[property]; + } catch (e) {} // eslint-disable-line no-empty + // For native code functions `delete` fails without throwing an error + // on Chrome < 43, PhantomJS, etc. + } else if (hasES5Support) { + Object.defineProperty(object, property, wrappedMethodDesc); + } + + // Use strict equality comparison to check failures then force a reset + // via direct assignment. + if (object[property] === method) { + object[property] = wrappedMethod; + } + }; + + method.restore.sinon = true; + + if (!hasES5Support) { + mirrorProperties(method, wrappedMethod); + } + + return method; + }; + + sinon.create = function create(proto) { + var F = function () {}; + F.prototype = proto; + return new F(); + }; + + sinon.deepEqual = function deepEqual(a, b) { + if (sinon.match && sinon.match.isMatcher(a)) { + return a.test(b); + } + + if (typeof a !== "object" || typeof b !== "object") { + return isReallyNaN(a) && isReallyNaN(b) || a === b; + } + + if (isElement(a) || isElement(b)) { + return a === b; + } + + if (a === b) { + return true; + } + + if ((a === null && b !== null) || (a !== null && b === null)) { + return false; + } + + if (a instanceof RegExp && b instanceof RegExp) { + return (a.source === b.source) && (a.global === b.global) && + (a.ignoreCase === b.ignoreCase) && (a.multiline === b.multiline); + } + + var aString = Object.prototype.toString.call(a); + if (aString !== Object.prototype.toString.call(b)) { + return false; + } + + if (aString === "[object Date]") { + return a.valueOf() === b.valueOf(); + } + + var prop; + var aLength = 0; + var bLength = 0; + + if (aString === "[object Array]" && a.length !== b.length) { + return false; + } + + for (prop in a) { + if (hasOwn.call(a, prop)) { + aLength += 1; + + if (!(prop in b)) { + return false; + } + + if (!deepEqual(a[prop], b[prop])) { + return false; + } + } + } + + for (prop in b) { + if (hasOwn.call(b, prop)) { + bLength += 1; + } + } + + return aLength === bLength; + }; + + sinon.functionName = function functionName(func) { + var name = func.displayName || func.name; + + // Use function decomposition as a last resort to get function + // name. Does not rely on function decomposition to work - if it + // doesn't debugging will be slightly less informative + // (i.e. toString will say 'spy' rather than 'myFunc'). + if (!name) { + var matches = func.toString().match(/function ([^\s\(]+)/); + name = matches && matches[1]; + } + + return name; + }; + + sinon.functionToString = function toString() { + if (this.getCall && this.callCount) { + var thisValue, + prop; + var i = this.callCount; + + while (i--) { + thisValue = this.getCall(i).thisValue; + + for (prop in thisValue) { + if (thisValue[prop] === this) { + return prop; + } + } + } + } + + return this.displayName || "sinon fake"; + }; + + sinon.objectKeys = function objectKeys(obj) { + if (obj !== Object(obj)) { + throw new TypeError("sinon.objectKeys called on a non-object"); + } + + var keys = []; + var key; + for (key in obj) { + if (hasOwn.call(obj, key)) { + keys.push(key); + } + } + + return keys; + }; + + sinon.getPropertyDescriptor = function getPropertyDescriptor(object, property) { + var proto = object; + var descriptor; + + while (proto && !(descriptor = Object.getOwnPropertyDescriptor(proto, property))) { + proto = Object.getPrototypeOf(proto); + } + return descriptor; + }; + + sinon.getConfig = function (custom) { + var config = {}; + custom = custom || {}; + var defaults = sinon.defaultConfig; + + for (var prop in defaults) { + if (defaults.hasOwnProperty(prop)) { + config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; + } + } + + return config; + }; + + sinon.defaultConfig = { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }; + + sinon.timesInWords = function timesInWords(count) { + return count === 1 && "once" || + count === 2 && "twice" || + count === 3 && "thrice" || + (count || 0) + " times"; + }; + + sinon.calledInOrder = function (spies) { + for (var i = 1, l = spies.length; i < l; i++) { + if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) { + return false; + } + } + + return true; + }; + + sinon.orderByFirstCall = function (spies) { + return spies.sort(function (a, b) { + // uuid, won't ever be equal + var aCall = a.getCall(0); + var bCall = b.getCall(0); + var aId = aCall && aCall.callId || -1; + var bId = bCall && bCall.callId || -1; + + return aId < bId ? -1 : 1; + }); + }; + + sinon.createStubInstance = function (constructor) { + if (typeof constructor !== "function") { + throw new TypeError("The constructor should be a function."); + } + return sinon.stub(sinon.create(constructor.prototype)); + }; + + sinon.restore = function (object) { + if (object !== null && typeof object === "object") { + for (var prop in object) { + if (isRestorable(object[prop])) { + object[prop].restore(); + } + } + } else if (isRestorable(object)) { + object.restore(); + } + }; + + return sinon; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports) { + makeApi(exports); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + + // Adapted from https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug + var hasDontEnumBug = (function () { + var obj = { + constructor: function () { + return "0"; + }, + toString: function () { + return "1"; + }, + valueOf: function () { + return "2"; + }, + toLocaleString: function () { + return "3"; + }, + prototype: function () { + return "4"; + }, + isPrototypeOf: function () { + return "5"; + }, + propertyIsEnumerable: function () { + return "6"; + }, + hasOwnProperty: function () { + return "7"; + }, + length: function () { + return "8"; + }, + unique: function () { + return "9"; + } + }; + + var result = []; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + result.push(obj[prop]()); + } + } + return result.join("") !== "0123456789"; + })(); + + /* Public: Extend target in place with all (own) properties from sources in-order. Thus, last source will + * override properties in previous sources. + * + * target - The Object to extend + * sources - Objects to copy properties from. + * + * Returns the extended target + */ + function extend(target /*, sources */) { + var sources = Array.prototype.slice.call(arguments, 1); + var source, i, prop; + + for (i = 0; i < sources.length; i++) { + source = sources[i]; + + for (prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + + // Make sure we copy (own) toString method even when in JScript with DontEnum bug + // See https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug + if (hasDontEnumBug && source.hasOwnProperty("toString") && source.toString !== target.toString) { + target.toString = source.toString; + } + } + + return target; + } + + sinon.extend = extend; + return sinon.extend; + } + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + module.exports = makeApi(sinon); + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + + function timesInWords(count) { + switch (count) { + case 1: + return "once"; + case 2: + return "twice"; + case 3: + return "thrice"; + default: + return (count || 0) + " times"; + } + } + + sinon.timesInWords = timesInWords; + return sinon.timesInWords; + } + + function loadDependencies(require, exports, module) { + var core = require("./util/core"); + module.exports = makeApi(core); + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + */ +/** + * Format functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2014 Christian Johansen + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + function typeOf(value) { + if (value === null) { + return "null"; + } else if (value === undefined) { + return "undefined"; + } + var string = Object.prototype.toString.call(value); + return string.substring(8, string.length - 1).toLowerCase(); + } + + sinon.typeOf = typeOf; + return sinon.typeOf; + } + + function loadDependencies(require, exports, module) { + var core = require("./util/core"); + module.exports = makeApi(core); + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + * @depend typeOf.js + */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Match functions + * + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2012 Maximilian Antoni + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + function assertType(value, type, name) { + var actual = sinon.typeOf(value); + if (actual !== type) { + throw new TypeError("Expected type of " + name + " to be " + + type + ", but was " + actual); + } + } + + var matcher = { + toString: function () { + return this.message; + } + }; + + function isMatcher(object) { + return matcher.isPrototypeOf(object); + } + + function matchObject(expectation, actual) { + if (actual === null || actual === undefined) { + return false; + } + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + var exp = expectation[key]; + var act = actual[key]; + if (isMatcher(exp)) { + if (!exp.test(act)) { + return false; + } + } else if (sinon.typeOf(exp) === "object") { + if (!matchObject(exp, act)) { + return false; + } + } else if (!sinon.deepEqual(exp, act)) { + return false; + } + } + } + return true; + } + + function match(expectation, message) { + var m = sinon.create(matcher); + var type = sinon.typeOf(expectation); + switch (type) { + case "object": + if (typeof expectation.test === "function") { + m.test = function (actual) { + return expectation.test(actual) === true; + }; + m.message = "match(" + sinon.functionName(expectation.test) + ")"; + return m; + } + var str = []; + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + str.push(key + ": " + expectation[key]); + } + } + m.test = function (actual) { + return matchObject(expectation, actual); + }; + m.message = "match(" + str.join(", ") + ")"; + break; + case "number": + m.test = function (actual) { + // we need type coercion here + return expectation == actual; // eslint-disable-line eqeqeq + }; + break; + case "string": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return actual.indexOf(expectation) !== -1; + }; + m.message = "match(\"" + expectation + "\")"; + break; + case "regexp": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return expectation.test(actual); + }; + break; + case "function": + m.test = expectation; + if (message) { + m.message = message; + } else { + m.message = "match(" + sinon.functionName(expectation) + ")"; + } + break; + default: + m.test = function (actual) { + return sinon.deepEqual(expectation, actual); + }; + } + if (!m.message) { + m.message = "match(" + expectation + ")"; + } + return m; + } + + matcher.or = function (m2) { + if (!arguments.length) { + throw new TypeError("Matcher expected"); + } else if (!isMatcher(m2)) { + m2 = match(m2); + } + var m1 = this; + var or = sinon.create(matcher); + or.test = function (actual) { + return m1.test(actual) || m2.test(actual); + }; + or.message = m1.message + ".or(" + m2.message + ")"; + return or; + }; + + matcher.and = function (m2) { + if (!arguments.length) { + throw new TypeError("Matcher expected"); + } else if (!isMatcher(m2)) { + m2 = match(m2); + } + var m1 = this; + var and = sinon.create(matcher); + and.test = function (actual) { + return m1.test(actual) && m2.test(actual); + }; + and.message = m1.message + ".and(" + m2.message + ")"; + return and; + }; + + match.isMatcher = isMatcher; + + match.any = match(function () { + return true; + }, "any"); + + match.defined = match(function (actual) { + return actual !== null && actual !== undefined; + }, "defined"); + + match.truthy = match(function (actual) { + return !!actual; + }, "truthy"); + + match.falsy = match(function (actual) { + return !actual; + }, "falsy"); + + match.same = function (expectation) { + return match(function (actual) { + return expectation === actual; + }, "same(" + expectation + ")"); + }; + + match.typeOf = function (type) { + assertType(type, "string", "type"); + return match(function (actual) { + return sinon.typeOf(actual) === type; + }, "typeOf(\"" + type + "\")"); + }; + + match.instanceOf = function (type) { + assertType(type, "function", "type"); + return match(function (actual) { + return actual instanceof type; + }, "instanceOf(" + sinon.functionName(type) + ")"); + }; + + function createPropertyMatcher(propertyTest, messagePrefix) { + return function (property, value) { + assertType(property, "string", "property"); + var onlyProperty = arguments.length === 1; + var message = messagePrefix + "(\"" + property + "\""; + if (!onlyProperty) { + message += ", " + value; + } + message += ")"; + return match(function (actual) { + if (actual === undefined || actual === null || + !propertyTest(actual, property)) { + return false; + } + return onlyProperty || sinon.deepEqual(value, actual[property]); + }, message); + }; + } + + match.has = createPropertyMatcher(function (actual, property) { + if (typeof actual === "object") { + return property in actual; + } + return actual[property] !== undefined; + }, "has"); + + match.hasOwn = createPropertyMatcher(function (actual, property) { + return actual.hasOwnProperty(property); + }, "hasOwn"); + + match.bool = match.typeOf("boolean"); + match.number = match.typeOf("number"); + match.string = match.typeOf("string"); + match.object = match.typeOf("object"); + match.func = match.typeOf("function"); + match.array = match.typeOf("array"); + match.regexp = match.typeOf("regexp"); + match.date = match.typeOf("date"); + + sinon.match = match; + return match; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + require("./typeOf"); + module.exports = makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + */ +/** + * Format functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2014 Christian Johansen + */ +(function (sinonGlobal, formatio) { + + function makeApi(sinon) { + function valueFormatter(value) { + return "" + value; + } + + function getFormatioFormatter() { + var formatter = formatio.configure({ + quoteStrings: false, + limitChildrenCount: 250 + }); + + function format() { + return formatter.ascii.apply(formatter, arguments); + } + + return format; + } + + function getNodeFormatter() { + try { + var util = require("util"); + } catch (e) { + /* Node, but no util module - would be very old, but better safe than sorry */ + } + + function format(v) { + var isObjectWithNativeToString = typeof v === "object" && v.toString === Object.prototype.toString; + return isObjectWithNativeToString ? util.inspect(v) : v; + } + + return util ? format : valueFormatter; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var formatter; + + if (isNode) { + try { + formatio = require("formatio"); + } + catch (e) {} // eslint-disable-line no-empty + } + + if (formatio) { + formatter = getFormatioFormatter(); + } else if (isNode) { + formatter = getNodeFormatter(); + } else { + formatter = valueFormatter; + } + + sinon.format = formatter; + return sinon.format; + } + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + module.exports = makeApi(sinon); + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon, // eslint-disable-line no-undef + typeof formatio === "object" && formatio // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + * @depend match.js + * @depend format.js + */ +/** + * Spy calls + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + * Copyright (c) 2013 Maximilian Antoni + */ +(function (sinonGlobal) { + + var slice = Array.prototype.slice; + + function makeApi(sinon) { + function throwYieldError(proxy, text, args) { + var msg = sinon.functionName(proxy) + text; + if (args.length) { + msg += " Received [" + slice.call(args).join(", ") + "]"; + } + throw new Error(msg); + } + + var callProto = { + calledOn: function calledOn(thisValue) { + if (sinon.match && sinon.match.isMatcher(thisValue)) { + return thisValue.test(this.thisValue); + } + return this.thisValue === thisValue; + }, + + calledWith: function calledWith() { + var l = arguments.length; + if (l > this.args.length) { + return false; + } + for (var i = 0; i < l; i += 1) { + if (!sinon.deepEqual(arguments[i], this.args[i])) { + return false; + } + } + + return true; + }, + + calledWithMatch: function calledWithMatch() { + var l = arguments.length; + if (l > this.args.length) { + return false; + } + for (var i = 0; i < l; i += 1) { + var actual = this.args[i]; + var expectation = arguments[i]; + if (!sinon.match || !sinon.match(expectation).test(actual)) { + return false; + } + } + return true; + }, + + calledWithExactly: function calledWithExactly() { + return arguments.length === this.args.length && + this.calledWith.apply(this, arguments); + }, + + notCalledWith: function notCalledWith() { + return !this.calledWith.apply(this, arguments); + }, + + notCalledWithMatch: function notCalledWithMatch() { + return !this.calledWithMatch.apply(this, arguments); + }, + + returned: function returned(value) { + return sinon.deepEqual(value, this.returnValue); + }, + + threw: function threw(error) { + if (typeof error === "undefined" || !this.exception) { + return !!this.exception; + } + + return this.exception === error || this.exception.name === error; + }, + + calledWithNew: function calledWithNew() { + return this.proxy.prototype && this.thisValue instanceof this.proxy; + }, + + calledBefore: function (other) { + return this.callId < other.callId; + }, + + calledAfter: function (other) { + return this.callId > other.callId; + }, + + callArg: function (pos) { + this.args[pos](); + }, + + callArgOn: function (pos, thisValue) { + this.args[pos].apply(thisValue); + }, + + callArgWith: function (pos) { + this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1))); + }, + + callArgOnWith: function (pos, thisValue) { + var args = slice.call(arguments, 2); + this.args[pos].apply(thisValue, args); + }, + + "yield": function () { + this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0))); + }, + + yieldOn: function (thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (typeof args[i] === "function") { + args[i].apply(thisValue, slice.call(arguments, 1)); + return; + } + } + throwYieldError(this.proxy, " cannot yield since no callback was passed.", args); + }, + + yieldTo: function (prop) { + this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1))); + }, + + yieldToOn: function (prop, thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (args[i] && typeof args[i][prop] === "function") { + args[i][prop].apply(thisValue, slice.call(arguments, 2)); + return; + } + } + throwYieldError(this.proxy, " cannot yield to '" + prop + + "' since no callback was passed.", args); + }, + + getStackFrames: function () { + // Omit the error message and the two top stack frames in sinon itself: + return this.stack && this.stack.split("\n").slice(3); + }, + + toString: function () { + var callStr = this.proxy ? this.proxy.toString() + "(" : ""; + var args = []; + + if (!this.args) { + return ":("; + } + + for (var i = 0, l = this.args.length; i < l; ++i) { + args.push(sinon.format(this.args[i])); + } + + callStr = callStr + args.join(", ") + ")"; + + if (typeof this.returnValue !== "undefined") { + callStr += " => " + sinon.format(this.returnValue); + } + + if (this.exception) { + callStr += " !" + this.exception.name; + + if (this.exception.message) { + callStr += "(" + this.exception.message + ")"; + } + } + if (this.stack) { + callStr += this.getStackFrames()[0].replace(/^\s*(?:at\s+|@)?/, " at "); + + } + + return callStr; + } + }; + + callProto.invokeCallback = callProto.yield; + + function createSpyCall(spy, thisValue, args, returnValue, exception, id, stack) { + if (typeof id !== "number") { + throw new TypeError("Call id is not a number"); + } + var proxyCall = sinon.create(callProto); + proxyCall.proxy = spy; + proxyCall.thisValue = thisValue; + proxyCall.args = args; + proxyCall.returnValue = returnValue; + proxyCall.exception = exception; + proxyCall.callId = id; + proxyCall.stack = stack; + + return proxyCall; + } + createSpyCall.toString = callProto.toString; // used by mocks + + sinon.spyCall = createSpyCall; + return createSpyCall; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + require("./match"); + require("./format"); + module.exports = makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend times_in_words.js + * @depend util/core.js + * @depend extend.js + * @depend call.js + * @depend format.js + */ +/** + * Spy functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + var push = Array.prototype.push; + var slice = Array.prototype.slice; + var callId = 0; + + function spy(object, property, types) { + if (!property && typeof object === "function") { + return spy.create(object); + } + + if (!object && !property) { + return spy.create(function () { }); + } + + if (types) { + var methodDesc = sinon.getPropertyDescriptor(object, property); + for (var i = 0; i < types.length; i++) { + methodDesc[types[i]] = spy.create(methodDesc[types[i]]); + } + return sinon.wrapMethod(object, property, methodDesc); + } + + return sinon.wrapMethod(object, property, spy.create(object[property])); + } + + function matchingFake(fakes, args, strict) { + if (!fakes) { + return undefined; + } + + for (var i = 0, l = fakes.length; i < l; i++) { + if (fakes[i].matches(args, strict)) { + return fakes[i]; + } + } + } + + function incrementCallCount() { + this.called = true; + this.callCount += 1; + this.notCalled = false; + this.calledOnce = this.callCount === 1; + this.calledTwice = this.callCount === 2; + this.calledThrice = this.callCount === 3; + } + + function createCallProperties() { + this.firstCall = this.getCall(0); + this.secondCall = this.getCall(1); + this.thirdCall = this.getCall(2); + this.lastCall = this.getCall(this.callCount - 1); + } + + var vars = "a,b,c,d,e,f,g,h,i,j,k,l"; + function createProxy(func, proxyLength) { + // Retain the function length: + var p; + if (proxyLength) { + eval("p = (function proxy(" + vars.substring(0, proxyLength * 2 - 1) + // eslint-disable-line no-eval + ") { return p.invoke(func, this, slice.call(arguments)); });"); + } else { + p = function proxy() { + return p.invoke(func, this, slice.call(arguments)); + }; + } + p.isSinonProxy = true; + return p; + } + + var uuid = 0; + + // Public API + var spyApi = { + reset: function () { + if (this.invoking) { + var err = new Error("Cannot reset Sinon function while invoking it. " + + "Move the call to .reset outside of the callback."); + err.name = "InvalidResetException"; + throw err; + } + + this.called = false; + this.notCalled = true; + this.calledOnce = false; + this.calledTwice = false; + this.calledThrice = false; + this.callCount = 0; + this.firstCall = null; + this.secondCall = null; + this.thirdCall = null; + this.lastCall = null; + this.args = []; + this.returnValues = []; + this.thisValues = []; + this.exceptions = []; + this.callIds = []; + this.stacks = []; + if (this.fakes) { + for (var i = 0; i < this.fakes.length; i++) { + this.fakes[i].reset(); + } + } + + return this; + }, + + create: function create(func, spyLength) { + var name; + + if (typeof func !== "function") { + func = function () { }; + } else { + name = sinon.functionName(func); + } + + if (!spyLength) { + spyLength = func.length; + } + + var proxy = createProxy(func, spyLength); + + sinon.extend(proxy, spy); + delete proxy.create; + sinon.extend(proxy, func); + + proxy.reset(); + proxy.prototype = func.prototype; + proxy.displayName = name || "spy"; + proxy.toString = sinon.functionToString; + proxy.instantiateFake = sinon.spy.create; + proxy.id = "spy#" + uuid++; + + return proxy; + }, + + invoke: function invoke(func, thisValue, args) { + var matching = matchingFake(this.fakes, args); + var exception, returnValue; + + incrementCallCount.call(this); + push.call(this.thisValues, thisValue); + push.call(this.args, args); + push.call(this.callIds, callId++); + + // Make call properties available from within the spied function: + createCallProperties.call(this); + + try { + this.invoking = true; + + if (matching) { + returnValue = matching.invoke(func, thisValue, args); + } else { + returnValue = (this.func || func).apply(thisValue, args); + } + + var thisCall = this.getCall(this.callCount - 1); + if (thisCall.calledWithNew() && typeof returnValue !== "object") { + returnValue = thisValue; + } + } catch (e) { + exception = e; + } finally { + delete this.invoking; + } + + push.call(this.exceptions, exception); + push.call(this.returnValues, returnValue); + push.call(this.stacks, new Error().stack); + + // Make return value and exception available in the calls: + createCallProperties.call(this); + + if (exception !== undefined) { + throw exception; + } + + return returnValue; + }, + + named: function named(name) { + this.displayName = name; + return this; + }, + + getCall: function getCall(i) { + if (i < 0 || i >= this.callCount) { + return null; + } + + return sinon.spyCall(this, this.thisValues[i], this.args[i], + this.returnValues[i], this.exceptions[i], + this.callIds[i], this.stacks[i]); + }, + + getCalls: function () { + var calls = []; + var i; + + for (i = 0; i < this.callCount; i++) { + calls.push(this.getCall(i)); + } + + return calls; + }, + + calledBefore: function calledBefore(spyFn) { + if (!this.called) { + return false; + } + + if (!spyFn.called) { + return true; + } + + return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1]; + }, + + calledAfter: function calledAfter(spyFn) { + if (!this.called || !spyFn.called) { + return false; + } + + return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; + }, + + withArgs: function () { + var args = slice.call(arguments); + + if (this.fakes) { + var match = matchingFake(this.fakes, args, true); + + if (match) { + return match; + } + } else { + this.fakes = []; + } + + var original = this; + var fake = this.instantiateFake(); + fake.matchingAguments = args; + fake.parent = this; + push.call(this.fakes, fake); + + fake.withArgs = function () { + return original.withArgs.apply(original, arguments); + }; + + for (var i = 0; i < this.args.length; i++) { + if (fake.matches(this.args[i])) { + incrementCallCount.call(fake); + push.call(fake.thisValues, this.thisValues[i]); + push.call(fake.args, this.args[i]); + push.call(fake.returnValues, this.returnValues[i]); + push.call(fake.exceptions, this.exceptions[i]); + push.call(fake.callIds, this.callIds[i]); + } + } + createCallProperties.call(fake); + + return fake; + }, + + matches: function (args, strict) { + var margs = this.matchingAguments; + + if (margs.length <= args.length && + sinon.deepEqual(margs, args.slice(0, margs.length))) { + return !strict || margs.length === args.length; + } + }, + + printf: function (format) { + var spyInstance = this; + var args = slice.call(arguments, 1); + var formatter; + + return (format || "").replace(/%(.)/g, function (match, specifyer) { + formatter = spyApi.formatters[specifyer]; + + if (typeof formatter === "function") { + return formatter.call(null, spyInstance, args); + } else if (!isNaN(parseInt(specifyer, 10))) { + return sinon.format(args[specifyer - 1]); + } + + return "%" + specifyer; + }); + } + }; + + function delegateToCalls(method, matchAny, actual, notCalled) { + spyApi[method] = function () { + if (!this.called) { + if (notCalled) { + return notCalled.apply(this, arguments); + } + return false; + } + + var currentCall; + var matches = 0; + + for (var i = 0, l = this.callCount; i < l; i += 1) { + currentCall = this.getCall(i); + + if (currentCall[actual || method].apply(currentCall, arguments)) { + matches += 1; + + if (matchAny) { + return true; + } + } + } + + return matches === this.callCount; + }; + } + + delegateToCalls("calledOn", true); + delegateToCalls("alwaysCalledOn", false, "calledOn"); + delegateToCalls("calledWith", true); + delegateToCalls("calledWithMatch", true); + delegateToCalls("alwaysCalledWith", false, "calledWith"); + delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch"); + delegateToCalls("calledWithExactly", true); + delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly"); + delegateToCalls("neverCalledWith", false, "notCalledWith", function () { + return true; + }); + delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch", function () { + return true; + }); + delegateToCalls("threw", true); + delegateToCalls("alwaysThrew", false, "threw"); + delegateToCalls("returned", true); + delegateToCalls("alwaysReturned", false, "returned"); + delegateToCalls("calledWithNew", true); + delegateToCalls("alwaysCalledWithNew", false, "calledWithNew"); + delegateToCalls("callArg", false, "callArgWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgWith = spyApi.callArg; + delegateToCalls("callArgOn", false, "callArgOnWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgOnWith = spyApi.callArgOn; + delegateToCalls("yield", false, "yield", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode. + spyApi.invokeCallback = spyApi.yield; + delegateToCalls("yieldOn", false, "yieldOn", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + delegateToCalls("yieldTo", false, "yieldTo", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + delegateToCalls("yieldToOn", false, "yieldToOn", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + + spyApi.formatters = { + c: function (spyInstance) { + return sinon.timesInWords(spyInstance.callCount); + }, + + n: function (spyInstance) { + return spyInstance.toString(); + }, + + C: function (spyInstance) { + var calls = []; + + for (var i = 0, l = spyInstance.callCount; i < l; ++i) { + var stringifiedCall = " " + spyInstance.getCall(i).toString(); + if (/\n/.test(calls[i - 1])) { + stringifiedCall = "\n" + stringifiedCall; + } + push.call(calls, stringifiedCall); + } + + return calls.length > 0 ? "\n" + calls.join("\n") : ""; + }, + + t: function (spyInstance) { + var objects = []; + + for (var i = 0, l = spyInstance.callCount; i < l; ++i) { + push.call(objects, sinon.format(spyInstance.thisValues[i])); + } + + return objects.join(", "); + }, + + "*": function (spyInstance, args) { + var formatted = []; + + for (var i = 0, l = args.length; i < l; ++i) { + push.call(formatted, sinon.format(args[i])); + } + + return formatted.join(", "); + } + }; + + sinon.extend(spy, spyApi); + + spy.spyCall = sinon.spyCall; + sinon.spy = spy; + + return spy; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var core = require("./util/core"); + require("./call"); + require("./extend"); + require("./times_in_words"); + require("./format"); + module.exports = makeApi(core); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + * @depend extend.js + */ +/** + * Stub behavior + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Tim Fischbach (mail@timfischbach.de) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + var slice = Array.prototype.slice; + var join = Array.prototype.join; + var useLeftMostCallback = -1; + var useRightMostCallback = -2; + + var nextTick = (function () { + if (typeof process === "object" && typeof process.nextTick === "function") { + return process.nextTick; + } + + if (typeof setImmediate === "function") { + return setImmediate; + } + + return function (callback) { + setTimeout(callback, 0); + }; + })(); + + function throwsException(error, message) { + if (typeof error === "string") { + this.exception = new Error(message || ""); + this.exception.name = error; + } else if (!error) { + this.exception = new Error("Error"); + } else { + this.exception = error; + } + + return this; + } + + function getCallback(behavior, args) { + var callArgAt = behavior.callArgAt; + + if (callArgAt >= 0) { + return args[callArgAt]; + } + + var argumentList; + + if (callArgAt === useLeftMostCallback) { + argumentList = args; + } + + if (callArgAt === useRightMostCallback) { + argumentList = slice.call(args).reverse(); + } + + var callArgProp = behavior.callArgProp; + + for (var i = 0, l = argumentList.length; i < l; ++i) { + if (!callArgProp && typeof argumentList[i] === "function") { + return argumentList[i]; + } + + if (callArgProp && argumentList[i] && + typeof argumentList[i][callArgProp] === "function") { + return argumentList[i][callArgProp]; + } + } + + return null; + } + + function makeApi(sinon) { + function getCallbackError(behavior, func, args) { + if (behavior.callArgAt < 0) { + var msg; + + if (behavior.callArgProp) { + msg = sinon.functionName(behavior.stub) + + " expected to yield to '" + behavior.callArgProp + + "', but no object with such a property was passed."; + } else { + msg = sinon.functionName(behavior.stub) + + " expected to yield, but no callback was passed."; + } + + if (args.length > 0) { + msg += " Received [" + join.call(args, ", ") + "]"; + } + + return msg; + } + + return "argument at index " + behavior.callArgAt + " is not a function: " + func; + } + + function callCallback(behavior, args) { + if (typeof behavior.callArgAt === "number") { + var func = getCallback(behavior, args); + + if (typeof func !== "function") { + throw new TypeError(getCallbackError(behavior, func, args)); + } + + if (behavior.callbackAsync) { + nextTick(function () { + func.apply(behavior.callbackContext, behavior.callbackArguments); + }); + } else { + func.apply(behavior.callbackContext, behavior.callbackArguments); + } + } + } + + var proto = { + create: function create(stub) { + var behavior = sinon.extend({}, sinon.behavior); + delete behavior.create; + behavior.stub = stub; + + return behavior; + }, + + isPresent: function isPresent() { + return (typeof this.callArgAt === "number" || + this.exception || + typeof this.returnArgAt === "number" || + this.returnThis || + this.returnValueDefined); + }, + + invoke: function invoke(context, args) { + callCallback(this, args); + + if (this.exception) { + throw this.exception; + } else if (typeof this.returnArgAt === "number") { + return args[this.returnArgAt]; + } else if (this.returnThis) { + return context; + } + + return this.returnValue; + }, + + onCall: function onCall(index) { + return this.stub.onCall(index); + }, + + onFirstCall: function onFirstCall() { + return this.stub.onFirstCall(); + }, + + onSecondCall: function onSecondCall() { + return this.stub.onSecondCall(); + }, + + onThirdCall: function onThirdCall() { + return this.stub.onThirdCall(); + }, + + withArgs: function withArgs(/* arguments */) { + throw new Error( + "Defining a stub by invoking \"stub.onCall(...).withArgs(...)\" " + + "is not supported. Use \"stub.withArgs(...).onCall(...)\" " + + "to define sequential behavior for calls with certain arguments." + ); + }, + + callsArg: function callsArg(pos) { + if (typeof pos !== "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAt = pos; + this.callbackArguments = []; + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + callsArgOn: function callsArgOn(pos, context) { + if (typeof pos !== "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context !== "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAt = pos; + this.callbackArguments = []; + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + callsArgWith: function callsArgWith(pos) { + if (typeof pos !== "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAt = pos; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + callsArgOnWith: function callsArgWith(pos, context) { + if (typeof pos !== "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context !== "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAt = pos; + this.callbackArguments = slice.call(arguments, 2); + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + yields: function () { + this.callArgAt = useLeftMostCallback; + this.callbackArguments = slice.call(arguments, 0); + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + yieldsRight: function () { + this.callArgAt = useRightMostCallback; + this.callbackArguments = slice.call(arguments, 0); + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + yieldsOn: function (context) { + if (typeof context !== "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAt = useLeftMostCallback; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + yieldsTo: function (prop) { + this.callArgAt = useLeftMostCallback; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = undefined; + this.callArgProp = prop; + this.callbackAsync = false; + + return this; + }, + + yieldsToOn: function (prop, context) { + if (typeof context !== "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAt = useLeftMostCallback; + this.callbackArguments = slice.call(arguments, 2); + this.callbackContext = context; + this.callArgProp = prop; + this.callbackAsync = false; + + return this; + }, + + throws: throwsException, + throwsException: throwsException, + + returns: function returns(value) { + this.returnValue = value; + this.returnValueDefined = true; + this.exception = undefined; + + return this; + }, + + returnsArg: function returnsArg(pos) { + if (typeof pos !== "number") { + throw new TypeError("argument index is not number"); + } + + this.returnArgAt = pos; + + return this; + }, + + returnsThis: function returnsThis() { + this.returnThis = true; + + return this; + } + }; + + function createAsyncVersion(syncFnName) { + return function () { + var result = this[syncFnName].apply(this, arguments); + this.callbackAsync = true; + return result; + }; + } + + // create asynchronous versions of callsArg* and yields* methods + for (var method in proto) { + // need to avoid creating anotherasync versions of the newly added async methods + if (proto.hasOwnProperty(method) && method.match(/^(callsArg|yields)/) && !method.match(/Async/)) { + proto[method + "Async"] = createAsyncVersion(method); + } + } + + sinon.behavior = proto; + return proto; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + require("./extend"); + module.exports = makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + function walkInternal(obj, iterator, context, originalObj, seen) { + var proto, prop; + + if (typeof Object.getOwnPropertyNames !== "function") { + // We explicitly want to enumerate through all of the prototype's properties + // in this case, therefore we deliberately leave out an own property check. + /* eslint-disable guard-for-in */ + for (prop in obj) { + iterator.call(context, obj[prop], prop, obj); + } + /* eslint-enable guard-for-in */ + + return; + } + + Object.getOwnPropertyNames(obj).forEach(function (k) { + if (!seen[k]) { + seen[k] = true; + var target = typeof Object.getOwnPropertyDescriptor(obj, k).get === "function" ? + originalObj : obj; + iterator.call(context, target[k], k, target); + } + }); + + proto = Object.getPrototypeOf(obj); + if (proto) { + walkInternal(proto, iterator, context, originalObj, seen); + } + } + + /* Public: walks the prototype chain of an object and iterates over every own property + * name encountered. The iterator is called in the same fashion that Array.prototype.forEach + * works, where it is passed the value, key, and own object as the 1st, 2nd, and 3rd positional + * argument, respectively. In cases where Object.getOwnPropertyNames is not available, walk will + * default to using a simple for..in loop. + * + * obj - The object to walk the prototype chain for. + * iterator - The function to be called on each pass of the walk. + * context - (Optional) When given, the iterator will be called with this object as the receiver. + */ + function walk(obj, iterator, context) { + return walkInternal(obj, iterator, context, obj, {}); + } + + sinon.walk = walk; + return sinon.walk; + } + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + module.exports = makeApi(sinon); + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + * @depend extend.js + * @depend spy.js + * @depend behavior.js + * @depend walk.js + */ +/** + * Stub functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + function stub(object, property, func) { + if (!!func && typeof func !== "function" && typeof func !== "object") { + throw new TypeError("Custom stub should be a function or a property descriptor"); + } + + var wrapper; + + if (func) { + if (typeof func === "function") { + wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; + } else { + wrapper = func; + if (sinon.spy && sinon.spy.create) { + var types = sinon.objectKeys(wrapper); + for (var i = 0; i < types.length; i++) { + wrapper[types[i]] = sinon.spy.create(wrapper[types[i]]); + } + } + } + } else { + var stubLength = 0; + if (typeof object === "object" && typeof object[property] === "function") { + stubLength = object[property].length; + } + wrapper = stub.create(stubLength); + } + + if (!object && typeof property === "undefined") { + return sinon.stub.create(); + } + + if (typeof property === "undefined" && typeof object === "object") { + sinon.walk(object || {}, function (value, prop, propOwner) { + // we don't want to stub things like toString(), valueOf(), etc. so we only stub if the object + // is not Object.prototype + if ( + propOwner !== Object.prototype && + prop !== "constructor" && + typeof sinon.getPropertyDescriptor(propOwner, prop).value === "function" + ) { + stub(object, prop); + } + }); + + return object; + } + + return sinon.wrapMethod(object, property, wrapper); + } + + + /*eslint-disable no-use-before-define*/ + function getParentBehaviour(stubInstance) { + return (stubInstance.parent && getCurrentBehavior(stubInstance.parent)); + } + + function getDefaultBehavior(stubInstance) { + return stubInstance.defaultBehavior || + getParentBehaviour(stubInstance) || + sinon.behavior.create(stubInstance); + } + + function getCurrentBehavior(stubInstance) { + var behavior = stubInstance.behaviors[stubInstance.callCount - 1]; + return behavior && behavior.isPresent() ? behavior : getDefaultBehavior(stubInstance); + } + /*eslint-enable no-use-before-define*/ + + var uuid = 0; + + var proto = { + create: function create(stubLength) { + var functionStub = function () { + return getCurrentBehavior(functionStub).invoke(this, arguments); + }; + + functionStub.id = "stub#" + uuid++; + var orig = functionStub; + functionStub = sinon.spy.create(functionStub, stubLength); + functionStub.func = orig; + + sinon.extend(functionStub, stub); + functionStub.instantiateFake = sinon.stub.create; + functionStub.displayName = "stub"; + functionStub.toString = sinon.functionToString; + + functionStub.defaultBehavior = null; + functionStub.behaviors = []; + + return functionStub; + }, + + resetBehavior: function () { + var i; + + this.defaultBehavior = null; + this.behaviors = []; + + delete this.returnValue; + delete this.returnArgAt; + this.returnThis = false; + + if (this.fakes) { + for (i = 0; i < this.fakes.length; i++) { + this.fakes[i].resetBehavior(); + } + } + }, + + onCall: function onCall(index) { + if (!this.behaviors[index]) { + this.behaviors[index] = sinon.behavior.create(this); + } + + return this.behaviors[index]; + }, + + onFirstCall: function onFirstCall() { + return this.onCall(0); + }, + + onSecondCall: function onSecondCall() { + return this.onCall(1); + }, + + onThirdCall: function onThirdCall() { + return this.onCall(2); + } + }; + + function createBehavior(behaviorMethod) { + return function () { + this.defaultBehavior = this.defaultBehavior || sinon.behavior.create(this); + this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments); + return this; + }; + } + + for (var method in sinon.behavior) { + if (sinon.behavior.hasOwnProperty(method) && + !proto.hasOwnProperty(method) && + method !== "create" && + method !== "withArgs" && + method !== "invoke") { + proto[method] = createBehavior(method); + } + } + + sinon.extend(stub, proto); + sinon.stub = stub; + + return stub; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var core = require("./util/core"); + require("./behavior"); + require("./spy"); + require("./extend"); + module.exports = makeApi(core); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend times_in_words.js + * @depend util/core.js + * @depend call.js + * @depend extend.js + * @depend match.js + * @depend spy.js + * @depend stub.js + * @depend format.js + */ +/** + * Mock functions. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + var push = [].push; + var match = sinon.match; + + function mock(object) { + // if (typeof console !== undefined && console.warn) { + // console.warn("mock will be removed from Sinon.JS v2.0"); + // } + + if (!object) { + return sinon.expectation.create("Anonymous mock"); + } + + return mock.create(object); + } + + function each(collection, callback) { + if (!collection) { + return; + } + + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + + function arrayEquals(arr1, arr2, compareLength) { + if (compareLength && (arr1.length !== arr2.length)) { + return false; + } + + for (var i = 0, l = arr1.length; i < l; i++) { + if (!sinon.deepEqual(arr1[i], arr2[i])) { + return false; + } + } + return true; + } + + sinon.extend(mock, { + create: function create(object) { + if (!object) { + throw new TypeError("object is null"); + } + + var mockObject = sinon.extend({}, mock); + mockObject.object = object; + delete mockObject.create; + + return mockObject; + }, + + expects: function expects(method) { + if (!method) { + throw new TypeError("method is falsy"); + } + + if (!this.expectations) { + this.expectations = {}; + this.proxies = []; + } + + if (!this.expectations[method]) { + this.expectations[method] = []; + var mockObject = this; + + sinon.wrapMethod(this.object, method, function () { + return mockObject.invokeMethod(method, this, arguments); + }); + + push.call(this.proxies, method); + } + + var expectation = sinon.expectation.create(method); + push.call(this.expectations[method], expectation); + + return expectation; + }, + + restore: function restore() { + var object = this.object; + + each(this.proxies, function (proxy) { + if (typeof object[proxy].restore === "function") { + object[proxy].restore(); + } + }); + }, + + verify: function verify() { + var expectations = this.expectations || {}; + var messages = []; + var met = []; + + each(this.proxies, function (proxy) { + each(expectations[proxy], function (expectation) { + if (!expectation.met()) { + push.call(messages, expectation.toString()); + } else { + push.call(met, expectation.toString()); + } + }); + }); + + this.restore(); + + if (messages.length > 0) { + sinon.expectation.fail(messages.concat(met).join("\n")); + } else if (met.length > 0) { + sinon.expectation.pass(messages.concat(met).join("\n")); + } + + return true; + }, + + invokeMethod: function invokeMethod(method, thisValue, args) { + var expectations = this.expectations && this.expectations[method] ? this.expectations[method] : []; + var expectationsWithMatchingArgs = []; + var currentArgs = args || []; + var i, available; + + for (i = 0; i < expectations.length; i += 1) { + var expectedArgs = expectations[i].expectedArguments || []; + if (arrayEquals(expectedArgs, currentArgs, expectations[i].expectsExactArgCount)) { + expectationsWithMatchingArgs.push(expectations[i]); + } + } + + for (i = 0; i < expectationsWithMatchingArgs.length; i += 1) { + if (!expectationsWithMatchingArgs[i].met() && + expectationsWithMatchingArgs[i].allowsCall(thisValue, args)) { + return expectationsWithMatchingArgs[i].apply(thisValue, args); + } + } + + var messages = []; + var exhausted = 0; + + for (i = 0; i < expectationsWithMatchingArgs.length; i += 1) { + if (expectationsWithMatchingArgs[i].allowsCall(thisValue, args)) { + available = available || expectationsWithMatchingArgs[i]; + } else { + exhausted += 1; + } + } + + if (available && exhausted === 0) { + return available.apply(thisValue, args); + } + + for (i = 0; i < expectations.length; i += 1) { + push.call(messages, " " + expectations[i].toString()); + } + + messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ + proxy: method, + args: args + })); + + sinon.expectation.fail(messages.join("\n")); + } + }); + + var times = sinon.timesInWords; + var slice = Array.prototype.slice; + + function callCountInWords(callCount) { + if (callCount === 0) { + return "never called"; + } + + return "called " + times(callCount); + } + + function expectedCallCountInWords(expectation) { + var min = expectation.minCalls; + var max = expectation.maxCalls; + + if (typeof min === "number" && typeof max === "number") { + var str = times(min); + + if (min !== max) { + str = "at least " + str + " and at most " + times(max); + } + + return str; + } + + if (typeof min === "number") { + return "at least " + times(min); + } + + return "at most " + times(max); + } + + function receivedMinCalls(expectation) { + var hasMinLimit = typeof expectation.minCalls === "number"; + return !hasMinLimit || expectation.callCount >= expectation.minCalls; + } + + function receivedMaxCalls(expectation) { + if (typeof expectation.maxCalls !== "number") { + return false; + } + + return expectation.callCount === expectation.maxCalls; + } + + function verifyMatcher(possibleMatcher, arg) { + var isMatcher = match && match.isMatcher(possibleMatcher); + + return isMatcher && possibleMatcher.test(arg) || true; + } + + sinon.expectation = { + minCalls: 1, + maxCalls: 1, + + create: function create(methodName) { + var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); + delete expectation.create; + expectation.method = methodName; + + return expectation; + }, + + invoke: function invoke(func, thisValue, args) { + this.verifyCallAllowed(thisValue, args); + + return sinon.spy.invoke.apply(this, arguments); + }, + + atLeast: function atLeast(num) { + if (typeof num !== "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.maxCalls = null; + this.limitsSet = true; + } + + this.minCalls = num; + + return this; + }, + + atMost: function atMost(num) { + if (typeof num !== "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.minCalls = null; + this.limitsSet = true; + } + + this.maxCalls = num; + + return this; + }, + + never: function never() { + return this.exactly(0); + }, + + once: function once() { + return this.exactly(1); + }, + + twice: function twice() { + return this.exactly(2); + }, + + thrice: function thrice() { + return this.exactly(3); + }, + + exactly: function exactly(num) { + if (typeof num !== "number") { + throw new TypeError("'" + num + "' is not a number"); + } + + this.atLeast(num); + return this.atMost(num); + }, + + met: function met() { + return !this.failed && receivedMinCalls(this); + }, + + verifyCallAllowed: function verifyCallAllowed(thisValue, args) { + if (receivedMaxCalls(this)) { + this.failed = true; + sinon.expectation.fail(this.method + " already called " + times(this.maxCalls)); + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " + + this.expectedThis); + } + + if (!("expectedArguments" in this)) { + return; + } + + if (!args) { + sinon.expectation.fail(this.method + " received no arguments, expected " + + sinon.format(this.expectedArguments)); + } + + if (args.length < this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + if (this.expectsExactArgCount && + args.length !== this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + + if (!verifyMatcher(this.expectedArguments[i], args[i])) { + sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + + ", didn't match " + this.expectedArguments.toString()); + } + + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + + ", expected " + sinon.format(this.expectedArguments)); + } + } + }, + + allowsCall: function allowsCall(thisValue, args) { + if (this.met() && receivedMaxCalls(this)) { + return false; + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + return false; + } + + if (!("expectedArguments" in this)) { + return true; + } + + args = args || []; + + if (args.length < this.expectedArguments.length) { + return false; + } + + if (this.expectsExactArgCount && + args.length !== this.expectedArguments.length) { + return false; + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!verifyMatcher(this.expectedArguments[i], args[i])) { + return false; + } + + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + return false; + } + } + + return true; + }, + + withArgs: function withArgs() { + this.expectedArguments = slice.call(arguments); + return this; + }, + + withExactArgs: function withExactArgs() { + this.withArgs.apply(this, arguments); + this.expectsExactArgCount = true; + return this; + }, + + on: function on(thisValue) { + this.expectedThis = thisValue; + return this; + }, + + toString: function () { + var args = (this.expectedArguments || []).slice(); + + if (!this.expectsExactArgCount) { + push.call(args, "[...]"); + } + + var callStr = sinon.spyCall.toString.call({ + proxy: this.method || "anonymous mock expectation", + args: args + }); + + var message = callStr.replace(", [...", "[, ...") + " " + + expectedCallCountInWords(this); + + if (this.met()) { + return "Expectation met: " + message; + } + + return "Expected " + message + " (" + + callCountInWords(this.callCount) + ")"; + }, + + verify: function verify() { + if (!this.met()) { + sinon.expectation.fail(this.toString()); + } else { + sinon.expectation.pass(this.toString()); + } + + return true; + }, + + pass: function pass(message) { + sinon.assert.pass(message); + }, + + fail: function fail(message) { + var exception = new Error(message); + exception.name = "ExpectationError"; + + throw exception; + } + }; + + sinon.mock = mock; + return mock; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + require("./times_in_words"); + require("./call"); + require("./extend"); + require("./match"); + require("./spy"); + require("./stub"); + require("./format"); + + module.exports = makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + * @depend spy.js + * @depend stub.js + * @depend mock.js + */ +/** + * Collections of stubs, spies and mocks. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + var push = [].push; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + function getFakes(fakeCollection) { + if (!fakeCollection.fakes) { + fakeCollection.fakes = []; + } + + return fakeCollection.fakes; + } + + function each(fakeCollection, method) { + var fakes = getFakes(fakeCollection); + + for (var i = 0, l = fakes.length; i < l; i += 1) { + if (typeof fakes[i][method] === "function") { + fakes[i][method](); + } + } + } + + function compact(fakeCollection) { + var fakes = getFakes(fakeCollection); + var i = 0; + while (i < fakes.length) { + fakes.splice(i, 1); + } + } + + function makeApi(sinon) { + var collection = { + verify: function resolve() { + each(this, "verify"); + }, + + restore: function restore() { + each(this, "restore"); + compact(this); + }, + + reset: function restore() { + each(this, "reset"); + }, + + verifyAndRestore: function verifyAndRestore() { + var exception; + + try { + this.verify(); + } catch (e) { + exception = e; + } + + this.restore(); + + if (exception) { + throw exception; + } + }, + + add: function add(fake) { + push.call(getFakes(this), fake); + return fake; + }, + + spy: function spy() { + return this.add(sinon.spy.apply(sinon, arguments)); + }, + + stub: function stub(object, property, value) { + if (property) { + var original = object[property]; + + if (typeof original !== "function") { + if (!hasOwnProperty.call(object, property)) { + throw new TypeError("Cannot stub non-existent own property " + property); + } + + object[property] = value; + + return this.add({ + restore: function () { + object[property] = original; + } + }); + } + } + if (!property && !!object && typeof object === "object") { + var stubbedObj = sinon.stub.apply(sinon, arguments); + + for (var prop in stubbedObj) { + if (typeof stubbedObj[prop] === "function") { + this.add(stubbedObj[prop]); + } + } + + return stubbedObj; + } + + return this.add(sinon.stub.apply(sinon, arguments)); + }, + + mock: function mock() { + return this.add(sinon.mock.apply(sinon, arguments)); + }, + + inject: function inject(obj) { + var col = this; + + obj.spy = function () { + return col.spy.apply(col, arguments); + }; + + obj.stub = function () { + return col.stub.apply(col, arguments); + }; + + obj.mock = function () { + return col.mock.apply(col, arguments); + }; + + return obj; + } + }; + + sinon.collection = collection; + return collection; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + require("./mock"); + require("./spy"); + require("./stub"); + module.exports = makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * Fake timer API + * setTimeout + * setInterval + * clearTimeout + * clearInterval + * tick + * reset + * Date + * + * Inspired by jsUnitMockTimeOut from JsUnit + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function () { + + function makeApi(s, lol) { + /*global lolex */ + var llx = typeof lolex !== "undefined" ? lolex : lol; + + s.useFakeTimers = function () { + var now; + var methods = Array.prototype.slice.call(arguments); + + if (typeof methods[0] === "string") { + now = 0; + } else { + now = methods.shift(); + } + + var clock = llx.install(now || 0, methods); + clock.restore = clock.uninstall; + return clock; + }; + + s.clock = { + create: function (now) { + return llx.createClock(now); + } + }; + + s.timers = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined), + clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate : undefined), + setInterval: setInterval, + clearInterval: clearInterval, + Date: Date + }; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, epxorts, module, lolex) { + var core = require("./core"); + makeApi(core, lolex); + module.exports = core; + } + + if (isAMD) { + define(loadDependencies); + } else if (isNode) { + loadDependencies(require, module.exports, module, require("lolex")); + } else { + makeApi(sinon); // eslint-disable-line no-undef + } +}()); + +/** + * Minimal Event interface implementation + * + * Original implementation by Sven Fuchs: https://gist.github.com/995028 + * Modifications and tests by Christian Johansen. + * + * @author Sven Fuchs (svenfuchs@artweb-design.de) + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2011 Sven Fuchs, Christian Johansen + */ +if (typeof sinon === "undefined") { + this.sinon = {}; +} + +(function () { + + var push = [].push; + + function makeApi(sinon) { + sinon.Event = function Event(type, bubbles, cancelable, target) { + this.initEvent(type, bubbles, cancelable, target); + }; + + sinon.Event.prototype = { + initEvent: function (type, bubbles, cancelable, target) { + this.type = type; + this.bubbles = bubbles; + this.cancelable = cancelable; + this.target = target; + }, + + stopPropagation: function () {}, + + preventDefault: function () { + this.defaultPrevented = true; + } + }; + + sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) { + this.initEvent(type, false, false, target); + this.loaded = typeof progressEventRaw.loaded === "number" ? progressEventRaw.loaded : null; + this.total = typeof progressEventRaw.total === "number" ? progressEventRaw.total : null; + this.lengthComputable = !!progressEventRaw.total; + }; + + sinon.ProgressEvent.prototype = new sinon.Event(); + + sinon.ProgressEvent.prototype.constructor = sinon.ProgressEvent; + + sinon.CustomEvent = function CustomEvent(type, customData, target) { + this.initEvent(type, false, false, target); + this.detail = customData.detail || null; + }; + + sinon.CustomEvent.prototype = new sinon.Event(); + + sinon.CustomEvent.prototype.constructor = sinon.CustomEvent; + + sinon.EventTarget = { + addEventListener: function addEventListener(event, listener) { + this.eventListeners = this.eventListeners || {}; + this.eventListeners[event] = this.eventListeners[event] || []; + push.call(this.eventListeners[event], listener); + }, + + removeEventListener: function removeEventListener(event, listener) { + var listeners = this.eventListeners && this.eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] === listener) { + return listeners.splice(i, 1); + } + } + }, + + dispatchEvent: function dispatchEvent(event) { + var type = event.type; + var listeners = this.eventListeners && this.eventListeners[type] || []; + + for (var i = 0; i < listeners.length; i++) { + if (typeof listeners[i] === "function") { + listeners[i].call(this, event); + } else { + listeners[i].handleEvent(event); + } + } + + return !!event.defaultPrevented; + } + }; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require) { + var sinon = require("./core"); + makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + } else if (isNode) { + loadDependencies(require); + } else { + makeApi(sinon); // eslint-disable-line no-undef + } +}()); + +/** + * @depend util/core.js + */ +/** + * Logs errors + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2014 Christian Johansen + */ +(function (sinonGlobal) { + + // cache a reference to setTimeout, so that our reference won't be stubbed out + // when using fake timers and errors will still get logged + // https://github.com/cjohansen/Sinon.JS/issues/381 + var realSetTimeout = setTimeout; + + function makeApi(sinon) { + + function log() {} + + function logError(label, err) { + var msg = label + " threw exception: "; + + function throwLoggedError() { + err.message = msg + err.message; + throw err; + } + + sinon.log(msg + "[" + err.name + "] " + err.message); + + if (err.stack) { + sinon.log(err.stack); + } + + if (logError.useImmediateExceptions) { + throwLoggedError(); + } else { + logError.setTimeout(throwLoggedError, 0); + } + } + + // When set to true, any errors logged will be thrown immediately; + // If set to false, the errors will be thrown in separate execution frame. + logError.useImmediateExceptions = false; + + // wrap realSetTimeout with something we can stub in tests + logError.setTimeout = function (func, timeout) { + realSetTimeout(func, timeout); + }; + + var exports = {}; + exports.log = sinon.log = log; + exports.logError = sinon.logError = logError; + + return exports; + } + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + module.exports = makeApi(sinon); + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend core.js + * @depend ../extend.js + * @depend event.js + * @depend ../log_error.js + */ +/** + * Fake XDomainRequest object + */ + +/** + * Returns the global to prevent assigning values to 'this' when this is undefined. + * This can occur when files are interpreted by node in strict mode. + * @private + */ +function getGlobal() { + + return typeof window !== "undefined" ? window : global; +} + +if (typeof sinon === "undefined") { + if (typeof this === "undefined") { + getGlobal().sinon = {}; + } else { + this.sinon = {}; + } +} + +// wrapper for global +(function (global) { + + var xdr = { XDomainRequest: global.XDomainRequest }; + xdr.GlobalXDomainRequest = global.XDomainRequest; + xdr.supportsXDR = typeof xdr.GlobalXDomainRequest !== "undefined"; + xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest : false; + + function makeApi(sinon) { + sinon.xdr = xdr; + + function FakeXDomainRequest() { + this.readyState = FakeXDomainRequest.UNSENT; + this.requestBody = null; + this.requestHeaders = {}; + this.status = 0; + this.timeout = null; + + if (typeof FakeXDomainRequest.onCreate === "function") { + FakeXDomainRequest.onCreate(this); + } + } + + function verifyState(x) { + if (x.readyState !== FakeXDomainRequest.OPENED) { + throw new Error("INVALID_STATE_ERR"); + } + + if (x.sendFlag) { + throw new Error("INVALID_STATE_ERR"); + } + } + + function verifyRequestSent(x) { + if (x.readyState === FakeXDomainRequest.UNSENT) { + throw new Error("Request not sent"); + } + if (x.readyState === FakeXDomainRequest.DONE) { + throw new Error("Request done"); + } + } + + function verifyResponseBodyType(body) { + if (typeof body !== "string") { + var error = new Error("Attempted to respond to fake XDomainRequest with " + + body + ", which is not a string."); + error.name = "InvalidBodyException"; + throw error; + } + } + + sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, { + open: function open(method, url) { + this.method = method; + this.url = url; + + this.responseText = null; + this.sendFlag = false; + + this.readyStateChange(FakeXDomainRequest.OPENED); + }, + + readyStateChange: function readyStateChange(state) { + this.readyState = state; + var eventName = ""; + switch (this.readyState) { + case FakeXDomainRequest.UNSENT: + break; + case FakeXDomainRequest.OPENED: + break; + case FakeXDomainRequest.LOADING: + if (this.sendFlag) { + //raise the progress event + eventName = "onprogress"; + } + break; + case FakeXDomainRequest.DONE: + if (this.isTimeout) { + eventName = "ontimeout"; + } else if (this.errorFlag || (this.status < 200 || this.status > 299)) { + eventName = "onerror"; + } else { + eventName = "onload"; + } + break; + } + + // raising event (if defined) + if (eventName) { + if (typeof this[eventName] === "function") { + try { + this[eventName](); + } catch (e) { + sinon.logError("Fake XHR " + eventName + " handler", e); + } + } + } + }, + + send: function send(data) { + verifyState(this); + + if (!/^(get|head)$/i.test(this.method)) { + this.requestBody = data; + } + this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; + + this.errorFlag = false; + this.sendFlag = true; + this.readyStateChange(FakeXDomainRequest.OPENED); + + if (typeof this.onSend === "function") { + this.onSend(this); + } + }, + + abort: function abort() { + this.aborted = true; + this.responseText = null; + this.errorFlag = true; + + if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) { + this.readyStateChange(sinon.FakeXDomainRequest.DONE); + this.sendFlag = false; + } + }, + + setResponseBody: function setResponseBody(body) { + verifyRequestSent(this); + verifyResponseBodyType(body); + + var chunkSize = this.chunkSize || 10; + var index = 0; + this.responseText = ""; + + do { + this.readyStateChange(FakeXDomainRequest.LOADING); + this.responseText += body.substring(index, index + chunkSize); + index += chunkSize; + } while (index < body.length); + + this.readyStateChange(FakeXDomainRequest.DONE); + }, + + respond: function respond(status, contentType, body) { + // content-type ignored, since XDomainRequest does not carry this + // we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease + // test integration across browsers + this.status = typeof status === "number" ? status : 200; + this.setResponseBody(body || ""); + }, + + simulatetimeout: function simulatetimeout() { + this.status = 0; + this.isTimeout = true; + // Access to this should actually throw an error + this.responseText = undefined; + this.readyStateChange(FakeXDomainRequest.DONE); + } + }); + + sinon.extend(FakeXDomainRequest, { + UNSENT: 0, + OPENED: 1, + LOADING: 3, + DONE: 4 + }); + + sinon.useFakeXDomainRequest = function useFakeXDomainRequest() { + sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) { + if (xdr.supportsXDR) { + global.XDomainRequest = xdr.GlobalXDomainRequest; + } + + delete sinon.FakeXDomainRequest.restore; + + if (keepOnCreate !== true) { + delete sinon.FakeXDomainRequest.onCreate; + } + }; + if (xdr.supportsXDR) { + global.XDomainRequest = sinon.FakeXDomainRequest; + } + return sinon.FakeXDomainRequest; + }; + + sinon.FakeXDomainRequest = FakeXDomainRequest; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./core"); + require("../extend"); + require("./event"); + require("../log_error"); + makeApi(sinon); + module.exports = sinon; + } + + if (isAMD) { + define(loadDependencies); + } else if (isNode) { + loadDependencies(require, module.exports, module); + } else { + makeApi(sinon); // eslint-disable-line no-undef + } +})(typeof global !== "undefined" ? global : self); + +/** + * @depend core.js + * @depend ../extend.js + * @depend event.js + * @depend ../log_error.js + */ +/** + * Fake XMLHttpRequest object + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal, global) { + + function getWorkingXHR(globalScope) { + var supportsXHR = typeof globalScope.XMLHttpRequest !== "undefined"; + if (supportsXHR) { + return globalScope.XMLHttpRequest; + } + + var supportsActiveX = typeof globalScope.ActiveXObject !== "undefined"; + if (supportsActiveX) { + return function () { + return new globalScope.ActiveXObject("MSXML2.XMLHTTP.3.0"); + }; + } + + return false; + } + + var supportsProgress = typeof ProgressEvent !== "undefined"; + var supportsCustomEvent = typeof CustomEvent !== "undefined"; + var supportsFormData = typeof FormData !== "undefined"; + var supportsArrayBuffer = typeof ArrayBuffer !== "undefined"; + var supportsBlob = (function () { + try { + return !!new Blob(); + } catch (e) { + return false; + } + })(); + var sinonXhr = { XMLHttpRequest: global.XMLHttpRequest }; + sinonXhr.GlobalXMLHttpRequest = global.XMLHttpRequest; + sinonXhr.GlobalActiveXObject = global.ActiveXObject; + sinonXhr.supportsActiveX = typeof sinonXhr.GlobalActiveXObject !== "undefined"; + sinonXhr.supportsXHR = typeof sinonXhr.GlobalXMLHttpRequest !== "undefined"; + sinonXhr.workingXHR = getWorkingXHR(global); + sinonXhr.supportsCORS = sinonXhr.supportsXHR && "withCredentials" in (new sinonXhr.GlobalXMLHttpRequest()); + + var unsafeHeaders = { + "Accept-Charset": true, + "Accept-Encoding": true, + Connection: true, + "Content-Length": true, + Cookie: true, + Cookie2: true, + "Content-Transfer-Encoding": true, + Date: true, + Expect: true, + Host: true, + "Keep-Alive": true, + Referer: true, + TE: true, + Trailer: true, + "Transfer-Encoding": true, + Upgrade: true, + "User-Agent": true, + Via: true + }; + + // An upload object is created for each + // FakeXMLHttpRequest and allows upload + // events to be simulated using uploadProgress + // and uploadError. + function UploadProgress() { + this.eventListeners = { + abort: [], + error: [], + load: [], + loadend: [], + progress: [] + }; + } + + UploadProgress.prototype.addEventListener = function addEventListener(event, listener) { + this.eventListeners[event].push(listener); + }; + + UploadProgress.prototype.removeEventListener = function removeEventListener(event, listener) { + var listeners = this.eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] === listener) { + return listeners.splice(i, 1); + } + } + }; + + UploadProgress.prototype.dispatchEvent = function dispatchEvent(event) { + var listeners = this.eventListeners[event.type] || []; + + for (var i = 0, listener; (listener = listeners[i]) != null; i++) { + listener(event); + } + }; + + // Note that for FakeXMLHttpRequest to work pre ES5 + // we lose some of the alignment with the spec. + // To ensure as close a match as possible, + // set responseType before calling open, send or respond; + function FakeXMLHttpRequest() { + this.readyState = FakeXMLHttpRequest.UNSENT; + this.requestHeaders = {}; + this.requestBody = null; + this.status = 0; + this.statusText = ""; + this.upload = new UploadProgress(); + this.responseType = ""; + this.response = ""; + if (sinonXhr.supportsCORS) { + this.withCredentials = false; + } + + var xhr = this; + var events = ["loadstart", "load", "abort", "error", "loadend"]; + + function addEventListener(eventName) { + xhr.addEventListener(eventName, function (event) { + var listener = xhr["on" + eventName]; + + if (listener && typeof listener === "function") { + listener.call(this, event); + } + }); + } + + for (var i = events.length - 1; i >= 0; i--) { + addEventListener(events[i]); + } + + if (typeof FakeXMLHttpRequest.onCreate === "function") { + FakeXMLHttpRequest.onCreate(this); + } + } + + function verifyState(xhr) { + if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR"); + } + + if (xhr.sendFlag) { + throw new Error("INVALID_STATE_ERR"); + } + } + + function getHeader(headers, header) { + header = header.toLowerCase(); + + for (var h in headers) { + if (h.toLowerCase() === header) { + return h; + } + } + + return null; + } + + // filtering to enable a white-list version of Sinon FakeXhr, + // where whitelisted requests are passed through to real XHR + function each(collection, callback) { + if (!collection) { + return; + } + + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + function some(collection, callback) { + for (var index = 0; index < collection.length; index++) { + if (callback(collection[index]) === true) { + return true; + } + } + return false; + } + // largest arity in XHR is 5 - XHR#open + var apply = function (obj, method, args) { + switch (args.length) { + case 0: return obj[method](); + case 1: return obj[method](args[0]); + case 2: return obj[method](args[0], args[1]); + case 3: return obj[method](args[0], args[1], args[2]); + case 4: return obj[method](args[0], args[1], args[2], args[3]); + case 5: return obj[method](args[0], args[1], args[2], args[3], args[4]); + } + }; + + FakeXMLHttpRequest.filters = []; + FakeXMLHttpRequest.addFilter = function addFilter(fn) { + this.filters.push(fn); + }; + var IE6Re = /MSIE 6/; + FakeXMLHttpRequest.defake = function defake(fakeXhr, xhrArgs) { + var xhr = new sinonXhr.workingXHR(); // eslint-disable-line new-cap + + each([ + "open", + "setRequestHeader", + "send", + "abort", + "getResponseHeader", + "getAllResponseHeaders", + "addEventListener", + "overrideMimeType", + "removeEventListener" + ], function (method) { + fakeXhr[method] = function () { + return apply(xhr, method, arguments); + }; + }); + + var copyAttrs = function (args) { + each(args, function (attr) { + try { + fakeXhr[attr] = xhr[attr]; + } catch (e) { + if (!IE6Re.test(navigator.userAgent)) { + throw e; + } + } + }); + }; + + var stateChange = function stateChange() { + fakeXhr.readyState = xhr.readyState; + if (xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) { + copyAttrs(["status", "statusText"]); + } + if (xhr.readyState >= FakeXMLHttpRequest.LOADING) { + copyAttrs(["responseText", "response"]); + } + if (xhr.readyState === FakeXMLHttpRequest.DONE) { + copyAttrs(["responseXML"]); + } + if (fakeXhr.onreadystatechange) { + fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr }); + } + }; + + if (xhr.addEventListener) { + for (var event in fakeXhr.eventListeners) { + if (fakeXhr.eventListeners.hasOwnProperty(event)) { + + /*eslint-disable no-loop-func*/ + each(fakeXhr.eventListeners[event], function (handler) { + xhr.addEventListener(event, handler); + }); + /*eslint-enable no-loop-func*/ + } + } + xhr.addEventListener("readystatechange", stateChange); + } else { + xhr.onreadystatechange = stateChange; + } + apply(xhr, "open", xhrArgs); + }; + FakeXMLHttpRequest.useFilters = false; + + function verifyRequestOpened(xhr) { + if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR - " + xhr.readyState); + } + } + + function verifyRequestSent(xhr) { + if (xhr.readyState === FakeXMLHttpRequest.DONE) { + throw new Error("Request done"); + } + } + + function verifyHeadersReceived(xhr) { + if (xhr.async && xhr.readyState !== FakeXMLHttpRequest.HEADERS_RECEIVED) { + throw new Error("No headers received"); + } + } + + function verifyResponseBodyType(body) { + if (typeof body !== "string") { + var error = new Error("Attempted to respond to fake XMLHttpRequest with " + + body + ", which is not a string."); + error.name = "InvalidBodyException"; + throw error; + } + } + + function convertToArrayBuffer(body) { + var buffer = new ArrayBuffer(body.length); + var view = new Uint8Array(buffer); + for (var i = 0; i < body.length; i++) { + var charCode = body.charCodeAt(i); + if (charCode >= 256) { + throw new TypeError("arraybuffer or blob responseTypes require binary string, " + + "invalid character " + body[i] + " found."); + } + view[i] = charCode; + } + return buffer; + } + + function isXmlContentType(contentType) { + return !contentType || /(text\/xml)|(application\/xml)|(\+xml)/.test(contentType); + } + + function convertResponseBody(responseType, contentType, body) { + if (responseType === "" || responseType === "text") { + return body; + } else if (supportsArrayBuffer && responseType === "arraybuffer") { + return convertToArrayBuffer(body); + } else if (responseType === "json") { + try { + return JSON.parse(body); + } catch (e) { + // Return parsing failure as null + return null; + } + } else if (supportsBlob && responseType === "blob") { + var blobOptions = {}; + if (contentType) { + blobOptions.type = contentType; + } + return new Blob([convertToArrayBuffer(body)], blobOptions); + } else if (responseType === "document") { + if (isXmlContentType(contentType)) { + return FakeXMLHttpRequest.parseXML(body); + } + return null; + } + throw new Error("Invalid responseType " + responseType); + } + + function clearResponse(xhr) { + if (xhr.responseType === "" || xhr.responseType === "text") { + xhr.response = xhr.responseText = ""; + } else { + xhr.response = xhr.responseText = null; + } + xhr.responseXML = null; + } + + FakeXMLHttpRequest.parseXML = function parseXML(text) { + // Treat empty string as parsing failure + if (text !== "") { + try { + if (typeof DOMParser !== "undefined") { + var parser = new DOMParser(); + return parser.parseFromString(text, "text/xml"); + } + var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = "false"; + xmlDoc.loadXML(text); + return xmlDoc; + } catch (e) { + // Unable to parse XML - no biggie + } + } + + return null; + }; + + FakeXMLHttpRequest.statusCodes = { + 100: "Continue", + 101: "Switching Protocols", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non-Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi-Status", + 300: "Multiple Choice", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 422: "Unprocessable Entity", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported" + }; + + function makeApi(sinon) { + sinon.xhr = sinonXhr; + + sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, { + async: true, + + open: function open(method, url, async, username, password) { + this.method = method; + this.url = url; + this.async = typeof async === "boolean" ? async : true; + this.username = username; + this.password = password; + clearResponse(this); + this.requestHeaders = {}; + this.sendFlag = false; + + if (FakeXMLHttpRequest.useFilters === true) { + var xhrArgs = arguments; + var defake = some(FakeXMLHttpRequest.filters, function (filter) { + return filter.apply(this, xhrArgs); + }); + if (defake) { + return FakeXMLHttpRequest.defake(this, arguments); + } + } + this.readyStateChange(FakeXMLHttpRequest.OPENED); + }, + + readyStateChange: function readyStateChange(state) { + this.readyState = state; + + var readyStateChangeEvent = new sinon.Event("readystatechange", false, false, this); + var event, progress; + + if (typeof this.onreadystatechange === "function") { + try { + this.onreadystatechange(readyStateChangeEvent); + } catch (e) { + sinon.logError("Fake XHR onreadystatechange handler", e); + } + } + + if (this.readyState === FakeXMLHttpRequest.DONE) { + // ensure loaded and total are numbers + progress = { + loaded: this.progress || 0, + total: this.progress || 0 + }; + + if (this.status === 0) { + event = this.aborted ? "abort" : "error"; + } + else { + event = "load"; + } + + if (supportsProgress) { + this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progress, this)); + this.upload.dispatchEvent(new sinon.ProgressEvent(event, progress, this)); + this.upload.dispatchEvent(new sinon.ProgressEvent("loadend", progress, this)); + } + + this.dispatchEvent(new sinon.ProgressEvent("progress", progress, this)); + this.dispatchEvent(new sinon.ProgressEvent(event, progress, this)); + this.dispatchEvent(new sinon.ProgressEvent("loadend", progress, this)); + } + + this.dispatchEvent(readyStateChangeEvent); + }, + + setRequestHeader: function setRequestHeader(header, value) { + verifyState(this); + + if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { + throw new Error("Refused to set unsafe header \"" + header + "\""); + } + + if (this.requestHeaders[header]) { + this.requestHeaders[header] += "," + value; + } else { + this.requestHeaders[header] = value; + } + }, + + // Helps testing + setResponseHeaders: function setResponseHeaders(headers) { + verifyRequestOpened(this); + this.responseHeaders = {}; + + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + this.responseHeaders[header] = headers[header]; + } + } + + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); + } else { + this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; + } + }, + + // Currently treats ALL data as a DOMString (i.e. no Document) + send: function send(data) { + verifyState(this); + + if (!/^(get|head)$/i.test(this.method)) { + var contentType = getHeader(this.requestHeaders, "Content-Type"); + if (this.requestHeaders[contentType]) { + var value = this.requestHeaders[contentType].split(";"); + this.requestHeaders[contentType] = value[0] + ";charset=utf-8"; + } else if (supportsFormData && !(data instanceof FormData)) { + this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; + } + + this.requestBody = data; + } + + this.errorFlag = false; + this.sendFlag = this.async; + clearResponse(this); + this.readyStateChange(FakeXMLHttpRequest.OPENED); + + if (typeof this.onSend === "function") { + this.onSend(this); + } + + this.dispatchEvent(new sinon.Event("loadstart", false, false, this)); + }, + + abort: function abort() { + this.aborted = true; + clearResponse(this); + this.errorFlag = true; + this.requestHeaders = {}; + this.responseHeaders = {}; + + if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) { + this.readyStateChange(FakeXMLHttpRequest.DONE); + this.sendFlag = false; + } + + this.readyState = FakeXMLHttpRequest.UNSENT; + }, + + error: function error() { + clearResponse(this); + this.errorFlag = true; + this.requestHeaders = {}; + this.responseHeaders = {}; + + this.readyStateChange(FakeXMLHttpRequest.DONE); + }, + + getResponseHeader: function getResponseHeader(header) { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return null; + } + + if (/^Set-Cookie2?$/i.test(header)) { + return null; + } + + header = getHeader(this.responseHeaders, header); + + return this.responseHeaders[header] || null; + }, + + getAllResponseHeaders: function getAllResponseHeaders() { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return ""; + } + + var headers = ""; + + for (var header in this.responseHeaders) { + if (this.responseHeaders.hasOwnProperty(header) && + !/^Set-Cookie2?$/i.test(header)) { + headers += header + ": " + this.responseHeaders[header] + "\r\n"; + } + } + + return headers; + }, + + setResponseBody: function setResponseBody(body) { + verifyRequestSent(this); + verifyHeadersReceived(this); + verifyResponseBodyType(body); + var contentType = this.getResponseHeader("Content-Type"); + + var isTextResponse = this.responseType === "" || this.responseType === "text"; + clearResponse(this); + if (this.async) { + var chunkSize = this.chunkSize || 10; + var index = 0; + + do { + this.readyStateChange(FakeXMLHttpRequest.LOADING); + + if (isTextResponse) { + this.responseText = this.response += body.substring(index, index + chunkSize); + } + index += chunkSize; + } while (index < body.length); + } + + this.response = convertResponseBody(this.responseType, contentType, body); + if (isTextResponse) { + this.responseText = this.response; + } + + if (this.responseType === "document") { + this.responseXML = this.response; + } else if (this.responseType === "" && isXmlContentType(contentType)) { + this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); + } + this.progress = body.length; + this.readyStateChange(FakeXMLHttpRequest.DONE); + }, + + respond: function respond(status, headers, body) { + this.status = typeof status === "number" ? status : 200; + this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; + this.setResponseHeaders(headers || {}); + this.setResponseBody(body || ""); + }, + + uploadProgress: function uploadProgress(progressEventRaw) { + if (supportsProgress) { + this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw)); + } + }, + + downloadProgress: function downloadProgress(progressEventRaw) { + if (supportsProgress) { + this.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw)); + } + }, + + uploadError: function uploadError(error) { + if (supportsCustomEvent) { + this.upload.dispatchEvent(new sinon.CustomEvent("error", {detail: error})); + } + } + }); + + sinon.extend(FakeXMLHttpRequest, { + UNSENT: 0, + OPENED: 1, + HEADERS_RECEIVED: 2, + LOADING: 3, + DONE: 4 + }); + + sinon.useFakeXMLHttpRequest = function () { + FakeXMLHttpRequest.restore = function restore(keepOnCreate) { + if (sinonXhr.supportsXHR) { + global.XMLHttpRequest = sinonXhr.GlobalXMLHttpRequest; + } + + if (sinonXhr.supportsActiveX) { + global.ActiveXObject = sinonXhr.GlobalActiveXObject; + } + + delete FakeXMLHttpRequest.restore; + + if (keepOnCreate !== true) { + delete FakeXMLHttpRequest.onCreate; + } + }; + if (sinonXhr.supportsXHR) { + global.XMLHttpRequest = FakeXMLHttpRequest; + } + + if (sinonXhr.supportsActiveX) { + global.ActiveXObject = function ActiveXObject(objId) { + if (objId === "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) { + + return new FakeXMLHttpRequest(); + } + + return new sinonXhr.GlobalActiveXObject(objId); + }; + } + + return FakeXMLHttpRequest; + }; + + sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./core"); + require("../extend"); + require("./event"); + require("../log_error"); + makeApi(sinon); + module.exports = sinon; + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon, // eslint-disable-line no-undef + typeof global !== "undefined" ? global : self +)); + +/** + * @depend fake_xdomain_request.js + * @depend fake_xml_http_request.js + * @depend ../format.js + * @depend ../log_error.js + */ +/** + * The Sinon "server" mimics a web server that receives requests from + * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, + * both synchronously and asynchronously. To respond synchronuously, canned + * answers have to be provided upfront. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function () { + + var push = [].push; + + function responseArray(handler) { + var response = handler; + + if (Object.prototype.toString.call(handler) !== "[object Array]") { + response = [200, {}, handler]; + } + + if (typeof response[2] !== "string") { + throw new TypeError("Fake server response body should be string, but was " + + typeof response[2]); + } + + return response; + } + + var wloc = typeof window !== "undefined" ? window.location : {}; + var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); + + function matchOne(response, reqMethod, reqUrl) { + var rmeth = response.method; + var matchMethod = !rmeth || rmeth.toLowerCase() === reqMethod.toLowerCase(); + var url = response.url; + var matchUrl = !url || url === reqUrl || (typeof url.test === "function" && url.test(reqUrl)); + + return matchMethod && matchUrl; + } + + function match(response, request) { + var requestUrl = request.url; + + if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { + requestUrl = requestUrl.replace(rCurrLoc, ""); + } + + if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { + if (typeof response.response === "function") { + var ru = response.url; + var args = [request].concat(ru && typeof ru.exec === "function" ? ru.exec(requestUrl).slice(1) : []); + return response.response.apply(response, args); + } + + return true; + } + + return false; + } + + function makeApi(sinon) { + sinon.fakeServer = { + create: function (config) { + var server = sinon.create(this); + server.configure(config); + if (!sinon.xhr.supportsCORS) { + this.xhr = sinon.useFakeXDomainRequest(); + } else { + this.xhr = sinon.useFakeXMLHttpRequest(); + } + server.requests = []; + + this.xhr.onCreate = function (xhrObj) { + server.addRequest(xhrObj); + }; + + return server; + }, + configure: function (config) { + var whitelist = { + "autoRespond": true, + "autoRespondAfter": true, + "respondImmediately": true, + "fakeHTTPMethods": true + }; + var setting; + + config = config || {}; + for (setting in config) { + if (whitelist.hasOwnProperty(setting) && config.hasOwnProperty(setting)) { + this[setting] = config[setting]; + } + } + }, + addRequest: function addRequest(xhrObj) { + var server = this; + push.call(this.requests, xhrObj); + + xhrObj.onSend = function () { + server.handleRequest(this); + + if (server.respondImmediately) { + server.respond(); + } else if (server.autoRespond && !server.responding) { + setTimeout(function () { + server.responding = false; + server.respond(); + }, server.autoRespondAfter || 10); + + server.responding = true; + } + }; + }, + + getHTTPMethod: function getHTTPMethod(request) { + if (this.fakeHTTPMethods && /post/i.test(request.method)) { + var matches = (request.requestBody || "").match(/_method=([^\b;]+)/); + return matches ? matches[1] : request.method; + } + + return request.method; + }, + + handleRequest: function handleRequest(xhr) { + if (xhr.async) { + if (!this.queue) { + this.queue = []; + } + + push.call(this.queue, xhr); + } else { + this.processRequest(xhr); + } + }, + + log: function log(response, request) { + var str; + + str = "Request:\n" + sinon.format(request) + "\n\n"; + str += "Response:\n" + sinon.format(response) + "\n\n"; + + sinon.log(str); + }, + + respondWith: function respondWith(method, url, body) { + if (arguments.length === 1 && typeof method !== "function") { + this.response = responseArray(method); + return; + } + + if (!this.responses) { + this.responses = []; + } + + if (arguments.length === 1) { + body = method; + url = method = null; + } + + if (arguments.length === 2) { + body = url; + url = method; + method = null; + } + + push.call(this.responses, { + method: method, + url: url, + response: typeof body === "function" ? body : responseArray(body) + }); + }, + + respond: function respond() { + if (arguments.length > 0) { + this.respondWith.apply(this, arguments); + } + + var queue = this.queue || []; + var requests = queue.splice(0, queue.length); + + for (var i = 0; i < requests.length; i++) { + this.processRequest(requests[i]); + } + }, + + processRequest: function processRequest(request) { + try { + if (request.aborted) { + return; + } + + var response = this.response || [404, {}, ""]; + + if (this.responses) { + for (var l = this.responses.length, i = l - 1; i >= 0; i--) { + if (match.call(this, this.responses[i], request)) { + response = this.responses[i].response; + break; + } + } + } + + if (request.readyState !== 4) { + this.log(response, request); + + request.respond(response[0], response[1], response[2]); + } + } catch (e) { + sinon.logError("Fake server request processing", e); + } + }, + + restore: function restore() { + return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); + } + }; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./core"); + require("./fake_xdomain_request"); + require("./fake_xml_http_request"); + require("../format"); + makeApi(sinon); + module.exports = sinon; + } + + if (isAMD) { + define(loadDependencies); + } else if (isNode) { + loadDependencies(require, module.exports, module); + } else { + makeApi(sinon); // eslint-disable-line no-undef + } +}()); + +/** + * @depend fake_server.js + * @depend fake_timers.js + */ +/** + * Add-on for sinon.fakeServer that automatically handles a fake timer along with + * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery + * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, + * it polls the object for completion with setInterval. Dispite the direct + * motivation, there is nothing jQuery-specific in this file, so it can be used + * in any environment where the ajax implementation depends on setInterval or + * setTimeout. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function () { + + function makeApi(sinon) { + function Server() {} + Server.prototype = sinon.fakeServer; + + sinon.fakeServerWithClock = new Server(); + + sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { + if (xhr.async) { + if (typeof setTimeout.clock === "object") { + this.clock = setTimeout.clock; + } else { + this.clock = sinon.useFakeTimers(); + this.resetClock = true; + } + + if (!this.longestTimeout) { + var clockSetTimeout = this.clock.setTimeout; + var clockSetInterval = this.clock.setInterval; + var server = this; + + this.clock.setTimeout = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetTimeout.apply(this, arguments); + }; + + this.clock.setInterval = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetInterval.apply(this, arguments); + }; + } + } + + return sinon.fakeServer.addRequest.call(this, xhr); + }; + + sinon.fakeServerWithClock.respond = function respond() { + var returnVal = sinon.fakeServer.respond.apply(this, arguments); + + if (this.clock) { + this.clock.tick(this.longestTimeout || 0); + this.longestTimeout = 0; + + if (this.resetClock) { + this.clock.restore(); + this.resetClock = false; + } + } + + return returnVal; + }; + + sinon.fakeServerWithClock.restore = function restore() { + if (this.clock) { + this.clock.restore(); + } + + return sinon.fakeServer.restore.apply(this, arguments); + }; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require) { + var sinon = require("./core"); + require("./fake_server"); + require("./fake_timers"); + makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + } else if (isNode) { + loadDependencies(require); + } else { + makeApi(sinon); // eslint-disable-line no-undef + } +}()); + +/** + * @depend util/core.js + * @depend extend.js + * @depend collection.js + * @depend util/fake_timers.js + * @depend util/fake_server_with_clock.js + */ +/** + * Manages fake collections as well as fake utilities such as Sinon's + * timers and fake XHR implementation in one convenient object. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + var push = [].push; + + function exposeValue(sandbox, config, key, value) { + if (!value) { + return; + } + + if (config.injectInto && !(key in config.injectInto)) { + config.injectInto[key] = value; + sandbox.injectedKeys.push(key); + } else { + push.call(sandbox.args, value); + } + } + + function prepareSandboxFromConfig(config) { + var sandbox = sinon.create(sinon.sandbox); + + if (config.useFakeServer) { + if (typeof config.useFakeServer === "object") { + sandbox.serverPrototype = config.useFakeServer; + } + + sandbox.useFakeServer(); + } + + if (config.useFakeTimers) { + if (typeof config.useFakeTimers === "object") { + sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); + } else { + sandbox.useFakeTimers(); + } + } + + return sandbox; + } + + sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { + useFakeTimers: function useFakeTimers() { + this.clock = sinon.useFakeTimers.apply(sinon, arguments); + + return this.add(this.clock); + }, + + serverPrototype: sinon.fakeServer, + + useFakeServer: function useFakeServer() { + var proto = this.serverPrototype || sinon.fakeServer; + + if (!proto || !proto.create) { + return null; + } + + this.server = proto.create(); + return this.add(this.server); + }, + + inject: function (obj) { + sinon.collection.inject.call(this, obj); + + if (this.clock) { + obj.clock = this.clock; + } + + if (this.server) { + obj.server = this.server; + obj.requests = this.server.requests; + } + + obj.match = sinon.match; + + return obj; + }, + + restore: function () { + if (arguments.length) { + throw new Error("sandbox.restore() does not take any parameters. Perhaps you meant stub.restore()"); + } + + sinon.collection.restore.apply(this, arguments); + this.restoreContext(); + }, + + restoreContext: function () { + if (this.injectedKeys) { + for (var i = 0, j = this.injectedKeys.length; i < j; i++) { + delete this.injectInto[this.injectedKeys[i]]; + } + this.injectedKeys = []; + } + }, + + create: function (config) { + if (!config) { + return sinon.create(sinon.sandbox); + } + + var sandbox = prepareSandboxFromConfig(config); + sandbox.args = sandbox.args || []; + sandbox.injectedKeys = []; + sandbox.injectInto = config.injectInto; + var prop, + value; + var exposed = sandbox.inject({}); + + if (config.properties) { + for (var i = 0, l = config.properties.length; i < l; i++) { + prop = config.properties[i]; + value = exposed[prop] || prop === "sandbox" && sandbox; + exposeValue(sandbox, config, prop, value); + } + } else { + exposeValue(sandbox, config, "sandbox", value); + } + + return sandbox; + }, + + match: sinon.match + }); + + sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; + + return sinon.sandbox; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + require("./extend"); + require("./util/fake_server_with_clock"); + require("./util/fake_timers"); + require("./collection"); + module.exports = makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend util/core.js + * @depend sandbox.js + */ +/** + * Test function, sandboxes fakes + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + function makeApi(sinon) { + var slice = Array.prototype.slice; + + function test(callback) { + var type = typeof callback; + + if (type !== "function") { + throw new TypeError("sinon.test needs to wrap a test function, got " + type); + } + + function sinonSandboxedTest() { + var config = sinon.getConfig(sinon.config); + config.injectInto = config.injectIntoThis && this || config.injectInto; + var sandbox = sinon.sandbox.create(config); + var args = slice.call(arguments); + var oldDone = args.length && args[args.length - 1]; + var exception, result; + + if (typeof oldDone === "function") { + args[args.length - 1] = function sinonDone(res) { + if (res) { + sandbox.restore(); + } else { + sandbox.verifyAndRestore(); + } + oldDone(res); + }; + } + + try { + result = callback.apply(this, args.concat(sandbox.args)); + } catch (e) { + exception = e; + } + + if (typeof exception !== "undefined") { + sandbox.restore(); + throw exception; + } else if (typeof oldDone !== "function") { + sandbox.verifyAndRestore(); + } + + return result; + } + + if (callback.length) { + return function sinonAsyncSandboxedTest(done) { // eslint-disable-line no-unused-vars + return sinonSandboxedTest.apply(this, arguments); + }; + } + + return sinonSandboxedTest; + } + + test.config = { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }; + + sinon.test = test; + return test; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var core = require("./util/core"); + require("./sandbox"); + module.exports = makeApi(core); + } + + if (isAMD) { + define(loadDependencies); + } else if (isNode) { + loadDependencies(require, module.exports, module); + } else if (sinonGlobal) { + makeApi(sinonGlobal); + } +}(typeof sinon === "object" && sinon || null)); // eslint-disable-line no-undef + +/** + * @depend util/core.js + * @depend test.js + */ +/** + * Test case, sandboxes all test functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal) { + + function createTest(property, setUp, tearDown) { + return function () { + if (setUp) { + setUp.apply(this, arguments); + } + + var exception, result; + + try { + result = property.apply(this, arguments); + } catch (e) { + exception = e; + } + + if (tearDown) { + tearDown.apply(this, arguments); + } + + if (exception) { + throw exception; + } + + return result; + }; + } + + function makeApi(sinon) { + function testCase(tests, prefix) { + if (!tests || typeof tests !== "object") { + throw new TypeError("sinon.testCase needs an object with test functions"); + } + + prefix = prefix || "test"; + var rPrefix = new RegExp("^" + prefix); + var methods = {}; + var setUp = tests.setUp; + var tearDown = tests.tearDown; + var testName, + property, + method; + + for (testName in tests) { + if (tests.hasOwnProperty(testName) && !/^(setUp|tearDown)$/.test(testName)) { + property = tests[testName]; + + if (typeof property === "function" && rPrefix.test(testName)) { + method = property; + + if (setUp || tearDown) { + method = createTest(property, setUp, tearDown); + } + + methods[testName] = sinon.test(method); + } else { + methods[testName] = tests[testName]; + } + } + } + + return methods; + } + + sinon.testCase = testCase; + return testCase; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var core = require("./util/core"); + require("./test"); + module.exports = makeApi(core); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon // eslint-disable-line no-undef +)); + +/** + * @depend times_in_words.js + * @depend util/core.js + * @depend match.js + * @depend format.js + */ +/** + * Assertions matching the test spy retrieval interface. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ +(function (sinonGlobal, global) { + + var slice = Array.prototype.slice; + + function makeApi(sinon) { + var assert; + + function verifyIsStub() { + var method; + + for (var i = 0, l = arguments.length; i < l; ++i) { + method = arguments[i]; + + if (!method) { + assert.fail("fake is not a spy"); + } + + if (method.proxy && method.proxy.isSinonProxy) { + verifyIsStub(method.proxy); + } else { + if (typeof method !== "function") { + assert.fail(method + " is not a function"); + } + + if (typeof method.getCall !== "function") { + assert.fail(method + " is not stubbed"); + } + } + + } + } + + function verifyIsValidAssertion(assertionMethod, assertionArgs) { + switch (assertionMethod) { + case "notCalled": + case "called": + case "calledOnce": + case "calledTwice": + case "calledThrice": + if (assertionArgs.length !== 0) { + assert.fail(assertionMethod + + " takes 1 argument but was called with " + + (assertionArgs.length + 1) + " arguments"); + } + break; + default: + break; + } + } + + function failAssertion(object, msg) { + object = object || global; + var failMethod = object.fail || assert.fail; + failMethod.call(object, msg); + } + + function mirrorPropAsAssertion(name, method, message) { + if (arguments.length === 2) { + message = method; + method = name; + } + + assert[name] = function (fake) { + verifyIsStub(fake); + + var args = slice.call(arguments, 1); + verifyIsValidAssertion(name, args); + + var failed = false; + + if (typeof method === "function") { + failed = !method(fake); + } else { + failed = typeof fake[method] === "function" ? + !fake[method].apply(fake, args) : !fake[method]; + } + + if (failed) { + failAssertion(this, (fake.printf || fake.proxy.printf).apply(fake, [message].concat(args))); + } else { + assert.pass(name); + } + }; + } + + function exposedName(prefix, prop) { + return !prefix || /^fail/.test(prop) ? prop : + prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); + } + + assert = { + failException: "AssertError", + + fail: function fail(message) { + var error = new Error(message); + error.name = this.failException || assert.failException; + + throw error; + }, + + pass: function pass() {}, + + callOrder: function assertCallOrder() { + verifyIsStub.apply(null, arguments); + var expected = ""; + var actual = ""; + + if (!sinon.calledInOrder(arguments)) { + try { + expected = [].join.call(arguments, ", "); + var calls = slice.call(arguments); + var i = calls.length; + while (i) { + if (!calls[--i].called) { + calls.splice(i, 1); + } + } + actual = sinon.orderByFirstCall(calls).join(", "); + } catch (e) { + // If this fails, we'll just fall back to the blank string + } + + failAssertion(this, "expected " + expected + " to be " + + "called in order but were called as " + actual); + } else { + assert.pass("callOrder"); + } + }, + + callCount: function assertCallCount(method, count) { + verifyIsStub(method); + + if (method.callCount !== count) { + var msg = "expected %n to be called " + sinon.timesInWords(count) + + " but was called %c%C"; + failAssertion(this, method.printf(msg)); + } else { + assert.pass("callCount"); + } + }, + + expose: function expose(target, options) { + if (!target) { + throw new TypeError("target is null or undefined"); + } + + var o = options || {}; + var prefix = typeof o.prefix === "undefined" && "assert" || o.prefix; + var includeFail = typeof o.includeFail === "undefined" || !!o.includeFail; + + for (var method in this) { + if (method !== "expose" && (includeFail || !/^(fail)/.test(method))) { + target[exposedName(prefix, method)] = this[method]; + } + } + + return target; + }, + + match: function match(actual, expectation) { + var matcher = sinon.match(expectation); + if (matcher.test(actual)) { + assert.pass("match"); + } else { + var formatted = [ + "expected value to match", + " expected = " + sinon.format(expectation), + " actual = " + sinon.format(actual) + ]; + + failAssertion(this, formatted.join("\n")); + } + } + }; + + mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called"); + mirrorPropAsAssertion("notCalled", function (spy) { + return !spy.called; + }, "expected %n to not have been called but was called %c%C"); + mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C"); + mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); + mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); + mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); + mirrorPropAsAssertion( + "alwaysCalledOn", + "expected %n to always be called with %1 as this but was called with %t" + ); + mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new"); + mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new"); + mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C"); + mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C"); + mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C"); + mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); + mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C"); + mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C"); + mirrorPropAsAssertion("threw", "%n did not throw exception%C"); + mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C"); + + sinon.assert = assert; + return assert; + } + + var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; + var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; + + function loadDependencies(require, exports, module) { + var sinon = require("./util/core"); + require("./match"); + require("./format"); + module.exports = makeApi(sinon); + } + + if (isAMD) { + define(loadDependencies); + return; + } + + if (isNode) { + loadDependencies(require, module.exports, module); + return; + } + + if (sinonGlobal) { + makeApi(sinonGlobal); + } +}( + typeof sinon === "object" && sinon, // eslint-disable-line no-undef + typeof global !== "undefined" ? global : self +)); + + return sinon; +})); From e25f9c40102c6566e63b7caa30f620da86fd0d60 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 14 Feb 2017 18:39:19 -0500 Subject: [PATCH 11/19] Use transition animation while loading This shows the transition animation while loading, so users can see something is going on. --- app/styles/base.css | 1 + app/ui.js | 1 + vnc.html | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/styles/base.css b/app/styles/base.css index 6bb23a47..4cec1735 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -774,6 +774,7 @@ select:active { justify-content: center; flex-direction: column; } +:root.noVNC_loading #noVNC_transition, :root.noVNC_connecting #noVNC_transition, :root.noVNC_disconnecting #noVNC_transition, :root.noVNC_reconnecting #noVNC_transition { diff --git a/app/ui.js b/app/ui.js index c53ddf70..d72f34dd 100644 --- a/app/ui.js +++ b/app/ui.js @@ -150,6 +150,7 @@ const UI = { UI.updateVisualState(); document.getElementById('noVNC_setting_host').focus(); + document.documentElement.classList.remove("noVNC_loading"); var autoconnect = WebUtil.getConfigVar('autoconnect', false); if (autoconnect === 'true' || autoconnect == '1') { diff --git a/vnc.html b/vnc.html index 64592f51..ab641e29 100644 --- a/vnc.html +++ b/vnc.html @@ -1,5 +1,5 @@ - + ` line comment + this.skipLineComment(3); + this.skipSpace(); + return this.nextToken(); + } + return this.finishOp(types.incDec, 2); + } + + if (next === 61) { + return this.finishOp(types.assign, 2); + } else { + return this.finishOp(types.plusMin, 1); + } + }; + + Tokenizer.prototype.readToken_lt_gt = function readToken_lt_gt(code) { + // '<>' + var next = this.input.charCodeAt(this.state.pos + 1); + var size = 1; + + if (next === code) { + size = code === 62 && this.input.charCodeAt(this.state.pos + 2) === 62 ? 3 : 2; + if (this.input.charCodeAt(this.state.pos + size) === 61) return this.finishOp(types.assign, size + 1); + return this.finishOp(types.bitShift, size); + } + + if (next === 33 && code === 60 && this.input.charCodeAt(this.state.pos + 2) === 45 && this.input.charCodeAt(this.state.pos + 3) === 45) { + if (this.inModule) this.unexpected(); + // ` regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) +} + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') + } + + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:) + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + try { + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') + } + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} + +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) + + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false + } + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} + +},{"brace-expansion":119,"path":469}],467:[function(require,module,exports){ +/** + * Helpers. + */ + +var s = 1000 +var m = s * 60 +var h = m * 60 +var d = h * 24 +var y = d * 365.25 + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} options + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function (val, options) { + options = options || {} + var type = typeof val + if (type === 'string' && val.length > 0) { + return parse(val) + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? + fmtLong(val) : + fmtShort(val) + } + throw new Error('val is not a non-empty string or a valid number. val=' + JSON.stringify(val)) +} + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str) + if (str.length > 10000) { + return + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str) + if (!match) { + return + } + var n = parseFloat(match[1]) + var type = (match[2] || 'ms').toLowerCase() + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y + case 'days': + case 'day': + case 'd': + return n * d + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n + default: + return undefined + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd' + } + if (ms >= h) { + return Math.round(ms / h) + 'h' + } + if (ms >= m) { + return Math.round(ms / m) + 'm' + } + if (ms >= s) { + return Math.round(ms / s) + 's' + } + return ms + 'ms' +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms' +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) { + return + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name + } + return Math.ceil(ms / n) + ' ' + name + 's' +} + +},{}],468:[function(require,module,exports){ +'use strict'; +module.exports = Number.isNaN || function (x) { + return x !== x; +}; + +},{}],469:[function(require,module,exports){ +(function (process){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; + +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; + +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; + + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; + +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; + + +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + +exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; + +function filter (xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} + +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' + ? function (str, start, len) { return str.substr(start, len) } + : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); + } +; + +}).call(this,require('_process')) +},{"_process":471}],470:[function(require,module,exports){ +(function (process){ +'use strict'; + +function posix(path) { + return path.charAt(0) === '/'; +} + +function win32(path) { + // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path); + var device = result[1] || ''; + var isUnc = Boolean(device && device.charAt(1) !== ':'); + + // UNC paths are always absolute + return Boolean(result[2] || isUnc); +} + +module.exports = process.platform === 'win32' ? win32 : posix; +module.exports.posix = posix; +module.exports.win32 = win32; + +}).call(this,require('_process')) +},{"_process":471}],471:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],472:[function(require,module,exports){ +'use strict'; +var isFinite = require('is-finite'); + +module.exports = function (str, n) { + if (typeof str !== 'string') { + throw new TypeError('Expected `input` to be a string'); + } + + if (n < 0 || !isFinite(n)) { + throw new TypeError('Expected `count` to be a positive finite number'); + } + + var ret = ''; + + do { + if (n & 1) { + ret += str; + } + + str += str; + } while ((n >>= 1)); + + return ret; +}; + +},{"is-finite":246}],473:[function(require,module,exports){ +'use strict'; +module.exports = function (str) { + var isExtendedLengthPath = /^\\\\\?\\/.test(str); + var hasNonAscii = /[^\x00-\x80]+/.test(str); + + if (isExtendedLengthPath || hasNonAscii) { + return str; + } + + return str.replace(/\\/g, '/'); +}; + +},{}],474:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var util = require('./util'); +var has = Object.prototype.hasOwnProperty; + +/** + * A data structure which is a combination of an array and a set. Adding a new + * member is O(1), testing for membership is O(1), and finding the index of an + * element is O(1). Removing elements from the set is not supported. Only + * strings are supported for membership. + */ +function ArraySet() { + this._array = []; + this._set = Object.create(null); +} + +/** + * Static method for creating ArraySet instances from an existing array. + */ +ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) { + var set = new ArraySet(); + for (var i = 0, len = aArray.length; i < len; i++) { + set.add(aArray[i], aAllowDuplicates); + } + return set; +}; + +/** + * Return how many unique items are in this ArraySet. If duplicates have been + * added, than those do not count towards the size. + * + * @returns Number + */ +ArraySet.prototype.size = function ArraySet_size() { + return Object.getOwnPropertyNames(this._set).length; +}; + +/** + * Add the given string to this set. + * + * @param String aStr + */ +ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) { + var sStr = util.toSetString(aStr); + var isDuplicate = has.call(this._set, sStr); + var idx = this._array.length; + if (!isDuplicate || aAllowDuplicates) { + this._array.push(aStr); + } + if (!isDuplicate) { + this._set[sStr] = idx; + } +}; + +/** + * Is the given string a member of this set? + * + * @param String aStr + */ +ArraySet.prototype.has = function ArraySet_has(aStr) { + var sStr = util.toSetString(aStr); + return has.call(this._set, sStr); +}; + +/** + * What is the index of the given string in the array? + * + * @param String aStr + */ +ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) { + var sStr = util.toSetString(aStr); + if (has.call(this._set, sStr)) { + return this._set[sStr]; + } + throw new Error('"' + aStr + '" is not in the set.'); +}; + +/** + * What is the element at the given index? + * + * @param Number aIdx + */ +ArraySet.prototype.at = function ArraySet_at(aIdx) { + if (aIdx >= 0 && aIdx < this._array.length) { + return this._array[aIdx]; + } + throw new Error('No element indexed by ' + aIdx); +}; + +/** + * Returns the array representation of this set (which has the proper indices + * indicated by indexOf). Note that this is a copy of the internal array used + * for storing the members so that no one can mess with internal state. + */ +ArraySet.prototype.toArray = function ArraySet_toArray() { + return this._array.slice(); +}; + +exports.ArraySet = ArraySet; + +},{"./util":483}],475:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + * + * Based on the Base 64 VLQ implementation in Closure Compiler: + * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java + * + * Copyright 2011 The Closure Compiler Authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var base64 = require('./base64'); + +// A single base 64 digit can contain 6 bits of data. For the base 64 variable +// length quantities we use in the source map spec, the first bit is the sign, +// the next four bits are the actual value, and the 6th bit is the +// continuation bit. The continuation bit tells us whether there are more +// digits in this value following this digit. +// +// Continuation +// | Sign +// | | +// V V +// 101011 + +var VLQ_BASE_SHIFT = 5; + +// binary: 100000 +var VLQ_BASE = 1 << VLQ_BASE_SHIFT; + +// binary: 011111 +var VLQ_BASE_MASK = VLQ_BASE - 1; + +// binary: 100000 +var VLQ_CONTINUATION_BIT = VLQ_BASE; + +/** + * Converts from a two-complement value to a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + */ +function toVLQSigned(aValue) { + return aValue < 0 + ? ((-aValue) << 1) + 1 + : (aValue << 1) + 0; +} + +/** + * Converts to a two-complement value from a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + */ +function fromVLQSigned(aValue) { + var isNegative = (aValue & 1) === 1; + var shifted = aValue >> 1; + return isNegative + ? -shifted + : shifted; +} + +/** + * Returns the base 64 VLQ encoded value. + */ +exports.encode = function base64VLQ_encode(aValue) { + var encoded = ""; + var digit; + + var vlq = toVLQSigned(aValue); + + do { + digit = vlq & VLQ_BASE_MASK; + vlq >>>= VLQ_BASE_SHIFT; + if (vlq > 0) { + // There are still more digits in this value, so we must make sure the + // continuation bit is marked. + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64.encode(digit); + } while (vlq > 0); + + return encoded; +}; + +/** + * Decodes the next base 64 VLQ value from the given string and returns the + * value and the rest of the string via the out parameter. + */ +exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { + var strLen = aStr.length; + var result = 0; + var shift = 0; + var continuation, digit; + + do { + if (aIndex >= strLen) { + throw new Error("Expected more digits in base 64 VLQ value."); + } + + digit = base64.decode(aStr.charCodeAt(aIndex++)); + if (digit === -1) { + throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1)); + } + + continuation = !!(digit & VLQ_CONTINUATION_BIT); + digit &= VLQ_BASE_MASK; + result = result + (digit << shift); + shift += VLQ_BASE_SHIFT; + } while (continuation); + + aOutParam.value = fromVLQSigned(result); + aOutParam.rest = aIndex; +}; + +},{"./base64":476}],476:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); + +/** + * Encode an integer in the range of 0 to 63 to a single base 64 digit. + */ +exports.encode = function (number) { + if (0 <= number && number < intToCharMap.length) { + return intToCharMap[number]; + } + throw new TypeError("Must be between 0 and 63: " + number); +}; + +/** + * Decode a single base 64 character code digit to an integer. Returns -1 on + * failure. + */ +exports.decode = function (charCode) { + var bigA = 65; // 'A' + var bigZ = 90; // 'Z' + + var littleA = 97; // 'a' + var littleZ = 122; // 'z' + + var zero = 48; // '0' + var nine = 57; // '9' + + var plus = 43; // '+' + var slash = 47; // '/' + + var littleOffset = 26; + var numberOffset = 52; + + // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ + if (bigA <= charCode && charCode <= bigZ) { + return (charCode - bigA); + } + + // 26 - 51: abcdefghijklmnopqrstuvwxyz + if (littleA <= charCode && charCode <= littleZ) { + return (charCode - littleA + littleOffset); + } + + // 52 - 61: 0123456789 + if (zero <= charCode && charCode <= nine) { + return (charCode - zero + numberOffset); + } + + // 62: + + if (charCode == plus) { + return 62; + } + + // 63: / + if (charCode == slash) { + return 63; + } + + // Invalid base64 digit. + return -1; +}; + +},{}],477:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +exports.GREATEST_LOWER_BOUND = 1; +exports.LEAST_UPPER_BOUND = 2; + +/** + * Recursive implementation of binary search. + * + * @param aLow Indices here and lower do not contain the needle. + * @param aHigh Indices here and higher do not contain the needle. + * @param aNeedle The element being searched for. + * @param aHaystack The non-empty array being searched. + * @param aCompare Function which takes two elements and returns -1, 0, or 1. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + */ +function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { + // This function terminates when one of the following is true: + // + // 1. We find the exact element we are looking for. + // + // 2. We did not find the exact element, but we can return the index of + // the next-closest element. + // + // 3. We did not find the exact element, and there is no next-closest + // element than the one we are searching for, so we return -1. + var mid = Math.floor((aHigh - aLow) / 2) + aLow; + var cmp = aCompare(aNeedle, aHaystack[mid], true); + if (cmp === 0) { + // Found the element we are looking for. + return mid; + } + else if (cmp > 0) { + // Our needle is greater than aHaystack[mid]. + if (aHigh - mid > 1) { + // The element is in the upper half. + return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); + } + + // The exact needle element was not found in this haystack. Determine if + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return aHigh < aHaystack.length ? aHigh : -1; + } else { + return mid; + } + } + else { + // Our needle is less than aHaystack[mid]. + if (mid - aLow > 1) { + // The element is in the lower half. + return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); + } + + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return mid; + } else { + return aLow < 0 ? -1 : aLow; + } + } +} + +/** + * This is an implementation of binary search which will always try and return + * the index of the closest element if there is no exact hit. This is because + * mappings between original and generated line/col pairs are single points, + * and there is an implicit region between each of them, so a miss just means + * that you aren't on the very start of a region. + * + * @param aNeedle The element you are looking for. + * @param aHaystack The array that is being searched. + * @param aCompare A function which takes the needle and an element in the + * array and returns -1, 0, or 1 depending on whether the needle is less + * than, equal to, or greater than the element, respectively. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. + */ +exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { + if (aHaystack.length === 0) { + return -1; + } + + var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, + aCompare, aBias || exports.GREATEST_LOWER_BOUND); + if (index < 0) { + return -1; + } + + // We have found either the exact element, or the next-closest element than + // the one we are searching for. However, there may be more than one such + // element. Make sure we always return the smallest of these. + while (index - 1 >= 0) { + if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { + break; + } + --index; + } + + return index; +}; + +},{}],478:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2014 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var util = require('./util'); + +/** + * Determine whether mappingB is after mappingA with respect to generated + * position. + */ +function generatedPositionAfter(mappingA, mappingB) { + // Optimized for most common case + var lineA = mappingA.generatedLine; + var lineB = mappingB.generatedLine; + var columnA = mappingA.generatedColumn; + var columnB = mappingB.generatedColumn; + return lineB > lineA || lineB == lineA && columnB >= columnA || + util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0; +} + +/** + * A data structure to provide a sorted view of accumulated mappings in a + * performance conscious manner. It trades a neglibable overhead in general + * case for a large speedup in case of mappings being added in order. + */ +function MappingList() { + this._array = []; + this._sorted = true; + // Serves as infimum + this._last = {generatedLine: -1, generatedColumn: 0}; +} + +/** + * Iterate through internal items. This method takes the same arguments that + * `Array.prototype.forEach` takes. + * + * NOTE: The order of the mappings is NOT guaranteed. + */ +MappingList.prototype.unsortedForEach = + function MappingList_forEach(aCallback, aThisArg) { + this._array.forEach(aCallback, aThisArg); + }; + +/** + * Add the given source mapping. + * + * @param Object aMapping + */ +MappingList.prototype.add = function MappingList_add(aMapping) { + if (generatedPositionAfter(this._last, aMapping)) { + this._last = aMapping; + this._array.push(aMapping); + } else { + this._sorted = false; + this._array.push(aMapping); + } +}; + +/** + * Returns the flat, sorted array of mappings. The mappings are sorted by + * generated position. + * + * WARNING: This method returns internal data without copying, for + * performance. The return value must NOT be mutated, and should be treated as + * an immutable borrow. If you want to take ownership, you must make your own + * copy. + */ +MappingList.prototype.toArray = function MappingList_toArray() { + if (!this._sorted) { + this._array.sort(util.compareByGeneratedPositionsInflated); + this._sorted = true; + } + return this._array; +}; + +exports.MappingList = MappingList; + +},{"./util":483}],479:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +// It turns out that some (most?) JavaScript engines don't self-host +// `Array.prototype.sort`. This makes sense because C++ will likely remain +// faster than JS when doing raw CPU-intensive sorting. However, when using a +// custom comparator function, calling back and forth between the VM's C++ and +// JIT'd JS is rather slow *and* loses JIT type information, resulting in +// worse generated code for the comparator function than would be optimal. In +// fact, when sorting with a comparator, these costs outweigh the benefits of +// sorting in C++. By using our own JS-implemented Quick Sort (below), we get +// a ~3500ms mean speed-up in `bench/bench.html`. + +/** + * Swap the elements indexed by `x` and `y` in the array `ary`. + * + * @param {Array} ary + * The array. + * @param {Number} x + * The index of the first item. + * @param {Number} y + * The index of the second item. + */ +function swap(ary, x, y) { + var temp = ary[x]; + ary[x] = ary[y]; + ary[y] = temp; +} + +/** + * Returns a random integer within the range `low .. high` inclusive. + * + * @param {Number} low + * The lower bound on the range. + * @param {Number} high + * The upper bound on the range. + */ +function randomIntInRange(low, high) { + return Math.round(low + (Math.random() * (high - low))); +} + +/** + * The Quick Sort algorithm. + * + * @param {Array} ary + * An array to sort. + * @param {function} comparator + * Function to use to compare two items. + * @param {Number} p + * Start index of the array + * @param {Number} r + * End index of the array + */ +function doQuickSort(ary, comparator, p, r) { + // If our lower bound is less than our upper bound, we (1) partition the + // array into two pieces and (2) recurse on each half. If it is not, this is + // the empty array and our base case. + + if (p < r) { + // (1) Partitioning. + // + // The partitioning chooses a pivot between `p` and `r` and moves all + // elements that are less than or equal to the pivot to the before it, and + // all the elements that are greater than it after it. The effect is that + // once partition is done, the pivot is in the exact place it will be when + // the array is put in sorted order, and it will not need to be moved + // again. This runs in O(n) time. + + // Always choose a random pivot so that an input array which is reverse + // sorted does not cause O(n^2) running time. + var pivotIndex = randomIntInRange(p, r); + var i = p - 1; + + swap(ary, pivotIndex, r); + var pivot = ary[r]; + + // Immediately after `j` is incremented in this loop, the following hold + // true: + // + // * Every element in `ary[p .. i]` is less than or equal to the pivot. + // + // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. + for (var j = p; j < r; j++) { + if (comparator(ary[j], pivot) <= 0) { + i += 1; + swap(ary, i, j); + } + } + + swap(ary, i + 1, j); + var q = i + 1; + + // (2) Recurse on each half. + + doQuickSort(ary, comparator, p, q - 1); + doQuickSort(ary, comparator, q + 1, r); + } +} + +/** + * Sort the given array in-place with the given comparator function. + * + * @param {Array} ary + * An array to sort. + * @param {function} comparator + * Function to use to compare two items. + */ +exports.quickSort = function (ary, comparator) { + doQuickSort(ary, comparator, 0, ary.length - 1); +}; + +},{}],480:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var util = require('./util'); +var binarySearch = require('./binary-search'); +var ArraySet = require('./array-set').ArraySet; +var base64VLQ = require('./base64-vlq'); +var quickSort = require('./quick-sort').quickSort; + +function SourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + return sourceMap.sections != null + ? new IndexedSourceMapConsumer(sourceMap) + : new BasicSourceMapConsumer(sourceMap); +} + +SourceMapConsumer.fromSourceMap = function(aSourceMap) { + return BasicSourceMapConsumer.fromSourceMap(aSourceMap); +} + +/** + * The version of the source mapping spec that we are consuming. + */ +SourceMapConsumer.prototype._version = 3; + +// `__generatedMappings` and `__originalMappings` are arrays that hold the +// parsed mapping coordinates from the source map's "mappings" attribute. They +// are lazily instantiated, accessed via the `_generatedMappings` and +// `_originalMappings` getters respectively, and we only parse the mappings +// and create these arrays once queried for a source location. We jump through +// these hoops because there can be many thousands of mappings, and parsing +// them is expensive, so we only want to do it if we must. +// +// Each object in the arrays is of the form: +// +// { +// generatedLine: The line number in the generated code, +// generatedColumn: The column number in the generated code, +// source: The path to the original source file that generated this +// chunk of code, +// originalLine: The line number in the original source that +// corresponds to this chunk of generated code, +// originalColumn: The column number in the original source that +// corresponds to this chunk of generated code, +// name: The name of the original symbol which generated this chunk of +// code. +// } +// +// All properties except for `generatedLine` and `generatedColumn` can be +// `null`. +// +// `_generatedMappings` is ordered by the generated positions. +// +// `_originalMappings` is ordered by the original positions. + +SourceMapConsumer.prototype.__generatedMappings = null; +Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', { + get: function () { + if (!this.__generatedMappings) { + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__generatedMappings; + } +}); + +SourceMapConsumer.prototype.__originalMappings = null; +Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { + get: function () { + if (!this.__originalMappings) { + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__originalMappings; + } +}); + +SourceMapConsumer.prototype._charIsMappingSeparator = + function SourceMapConsumer_charIsMappingSeparator(aStr, index) { + var c = aStr.charAt(index); + return c === ";" || c === ","; + }; + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +SourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + throw new Error("Subclasses must implement _parseMappings"); + }; + +SourceMapConsumer.GENERATED_ORDER = 1; +SourceMapConsumer.ORIGINAL_ORDER = 2; + +SourceMapConsumer.GREATEST_LOWER_BOUND = 1; +SourceMapConsumer.LEAST_UPPER_BOUND = 2; + +/** + * Iterate over each mapping between an original source/line/column and a + * generated line/column in this source map. + * + * @param Function aCallback + * The function that is called with each mapping. + * @param Object aContext + * Optional. If specified, this object will be the value of `this` every + * time that `aCallback` is called. + * @param aOrder + * Either `SourceMapConsumer.GENERATED_ORDER` or + * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to + * iterate over the mappings sorted by the generated file's line/column + * order or the original's source/line/column order, respectively. Defaults to + * `SourceMapConsumer.GENERATED_ORDER`. + */ +SourceMapConsumer.prototype.eachMapping = + function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) { + var context = aContext || null; + var order = aOrder || SourceMapConsumer.GENERATED_ORDER; + + var mappings; + switch (order) { + case SourceMapConsumer.GENERATED_ORDER: + mappings = this._generatedMappings; + break; + case SourceMapConsumer.ORIGINAL_ORDER: + mappings = this._originalMappings; + break; + default: + throw new Error("Unknown order of iteration."); + } + + var sourceRoot = this.sourceRoot; + mappings.map(function (mapping) { + var source = mapping.source === null ? null : this._sources.at(mapping.source); + if (source != null && sourceRoot != null) { + source = util.join(sourceRoot, source); + } + return { + source: source, + generatedLine: mapping.generatedLine, + generatedColumn: mapping.generatedColumn, + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: mapping.name === null ? null : this._names.at(mapping.name) + }; + }, this).forEach(aCallback, context); + }; + +/** + * Returns all generated line and column information for the original source, + * line, and column provided. If no column is provided, returns all mappings + * corresponding to a either the line we are searching for or the next + * closest line that has any mappings. Otherwise, returns all mappings + * corresponding to the given line and either the column we are searching for + * or the next closest column that has any offsets. + * + * The only argument is an object with the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: Optional. the column number in the original source. + * + * and an array of objects is returned, each with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ +SourceMapConsumer.prototype.allGeneratedPositionsFor = + function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { + var line = util.getArg(aArgs, 'line'); + + // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping + // returns the index of the closest mapping less than the needle. By + // setting needle.originalColumn to 0, we thus find the last mapping for + // the given line, provided such a mapping exists. + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: line, + originalColumn: util.getArg(aArgs, 'column', 0) + }; + + if (this.sourceRoot != null) { + needle.source = util.relative(this.sourceRoot, needle.source); + } + if (!this._sources.has(needle.source)) { + return []; + } + needle.source = this._sources.indexOf(needle.source); + + var mappings = []; + + var index = this._findMapping(needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + binarySearch.LEAST_UPPER_BOUND); + if (index >= 0) { + var mapping = this._originalMappings[index]; + + if (aArgs.column === undefined) { + var originalLine = mapping.originalLine; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line than the one we found. Since + // mappings are sorted, this is guaranteed to find all mappings for + // the line we found. + while (mapping && mapping.originalLine === originalLine) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } else { + var originalColumn = mapping.originalColumn; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line than the one we were searching for. + // Since mappings are sorted, this is guaranteed to find all mappings for + // the line we are searching for. + while (mapping && + mapping.originalLine === line && + mapping.originalColumn == originalColumn) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } + } + + return mappings; + }; + +exports.SourceMapConsumer = SourceMapConsumer; + +/** + * A BasicSourceMapConsumer instance represents a parsed source map which we can + * query for information about the original file positions by giving it a file + * position in the generated source. + * + * The only parameter is the raw source map (either as a JSON string, or + * already parsed to an object). According to the spec, source maps have the + * following attributes: + * + * - version: Which version of the source map spec this map is following. + * - sources: An array of URLs to the original source files. + * - names: An array of identifiers which can be referrenced by individual mappings. + * - sourceRoot: Optional. The URL root from which all sources are relative. + * - sourcesContent: Optional. An array of contents of the original source files. + * - mappings: A string of base64 VLQs which contain the actual mappings. + * - file: Optional. The generated file this source map is associated with. + * + * Here is an example source map, taken from the source map spec[0]: + * + * { + * version : 3, + * file: "out.js", + * sourceRoot : "", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AA,AB;;ABCDE;" + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# + */ +function BasicSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sources = util.getArg(sourceMap, 'sources'); + // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which + // requires the array) to play nice here. + var names = util.getArg(sourceMap, 'names', []); + var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); + var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); + var mappings = util.getArg(sourceMap, 'mappings'); + var file = util.getArg(sourceMap, 'file', null); + + // Once again, Sass deviates from the spec and supplies the version as a + // string rather than a number, so we use loose equality checking here. + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + sources = sources + .map(String) + // Some source maps produce relative source paths like "./foo.js" instead of + // "foo.js". Normalize these first so that future comparisons will succeed. + // See bugzil.la/1090768. + .map(util.normalize) + // Always ensure that absolute sources are internally stored relative to + // the source root, if the source root is absolute. Not doing this would + // be particularly problematic when the source root is a prefix of the + // source (valid, but why??). See github issue #199 and bugzil.la/1188982. + .map(function (source) { + return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source) + ? util.relative(sourceRoot, source) + : source; + }); + + // Pass `true` below to allow duplicate names and sources. While source maps + // are intended to be compressed and deduplicated, the TypeScript compiler + // sometimes generates source maps with duplicates in them. See Github issue + // #72 and bugzil.la/889492. + this._names = ArraySet.fromArray(names.map(String), true); + this._sources = ArraySet.fromArray(sources, true); + + this.sourceRoot = sourceRoot; + this.sourcesContent = sourcesContent; + this._mappings = mappings; + this.file = file; +} + +BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); +BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; + +/** + * Create a BasicSourceMapConsumer from a SourceMapGenerator. + * + * @param SourceMapGenerator aSourceMap + * The source map that will be consumed. + * @returns BasicSourceMapConsumer + */ +BasicSourceMapConsumer.fromSourceMap = + function SourceMapConsumer_fromSourceMap(aSourceMap) { + var smc = Object.create(BasicSourceMapConsumer.prototype); + + var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); + var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); + smc.sourceRoot = aSourceMap._sourceRoot; + smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), + smc.sourceRoot); + smc.file = aSourceMap._file; + + // Because we are modifying the entries (by converting string sources and + // names to indices into the sources and names ArraySets), we have to make + // a copy of the entry or else bad things happen. Shared mutable state + // strikes again! See github issue #191. + + var generatedMappings = aSourceMap._mappings.toArray().slice(); + var destGeneratedMappings = smc.__generatedMappings = []; + var destOriginalMappings = smc.__originalMappings = []; + + for (var i = 0, length = generatedMappings.length; i < length; i++) { + var srcMapping = generatedMappings[i]; + var destMapping = new Mapping; + destMapping.generatedLine = srcMapping.generatedLine; + destMapping.generatedColumn = srcMapping.generatedColumn; + + if (srcMapping.source) { + destMapping.source = sources.indexOf(srcMapping.source); + destMapping.originalLine = srcMapping.originalLine; + destMapping.originalColumn = srcMapping.originalColumn; + + if (srcMapping.name) { + destMapping.name = names.indexOf(srcMapping.name); + } + + destOriginalMappings.push(destMapping); + } + + destGeneratedMappings.push(destMapping); + } + + quickSort(smc.__originalMappings, util.compareByOriginalPositions); + + return smc; + }; + +/** + * The version of the source mapping spec that we are consuming. + */ +BasicSourceMapConsumer.prototype._version = 3; + +/** + * The list of original sources. + */ +Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { + get: function () { + return this._sources.toArray().map(function (s) { + return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s; + }, this); + } +}); + +/** + * Provide the JIT with a nice shape / hidden class. + */ +function Mapping() { + this.generatedLine = 0; + this.generatedColumn = 0; + this.source = null; + this.originalLine = null; + this.originalColumn = null; + this.name = null; +} + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +BasicSourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + var generatedLine = 1; + var previousGeneratedColumn = 0; + var previousOriginalLine = 0; + var previousOriginalColumn = 0; + var previousSource = 0; + var previousName = 0; + var length = aStr.length; + var index = 0; + var cachedSegments = {}; + var temp = {}; + var originalMappings = []; + var generatedMappings = []; + var mapping, str, segment, end, value; + + while (index < length) { + if (aStr.charAt(index) === ';') { + generatedLine++; + index++; + previousGeneratedColumn = 0; + } + else if (aStr.charAt(index) === ',') { + index++; + } + else { + mapping = new Mapping(); + mapping.generatedLine = generatedLine; + + // Because each offset is encoded relative to the previous one, + // many segments often have the same encoding. We can exploit this + // fact by caching the parsed variable length fields of each segment, + // allowing us to avoid a second parse if we encounter the same + // segment again. + for (end = index; end < length; end++) { + if (this._charIsMappingSeparator(aStr, end)) { + break; + } + } + str = aStr.slice(index, end); + + segment = cachedSegments[str]; + if (segment) { + index += str.length; + } else { + segment = []; + while (index < end) { + base64VLQ.decode(aStr, index, temp); + value = temp.value; + index = temp.rest; + segment.push(value); + } + + if (segment.length === 2) { + throw new Error('Found a source, but no line and column'); + } + + if (segment.length === 3) { + throw new Error('Found a source and line, but no column'); + } + + cachedSegments[str] = segment; + } + + // Generated column. + mapping.generatedColumn = previousGeneratedColumn + segment[0]; + previousGeneratedColumn = mapping.generatedColumn; + + if (segment.length > 1) { + // Original source. + mapping.source = previousSource + segment[1]; + previousSource += segment[1]; + + // Original line. + mapping.originalLine = previousOriginalLine + segment[2]; + previousOriginalLine = mapping.originalLine; + // Lines are stored 0-based + mapping.originalLine += 1; + + // Original column. + mapping.originalColumn = previousOriginalColumn + segment[3]; + previousOriginalColumn = mapping.originalColumn; + + if (segment.length > 4) { + // Original name. + mapping.name = previousName + segment[4]; + previousName += segment[4]; + } + } + + generatedMappings.push(mapping); + if (typeof mapping.originalLine === 'number') { + originalMappings.push(mapping); + } + } + } + + quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated); + this.__generatedMappings = generatedMappings; + + quickSort(originalMappings, util.compareByOriginalPositions); + this.__originalMappings = originalMappings; + }; + +/** + * Find the mapping that best matches the hypothetical "needle" mapping that + * we are searching for in the given "haystack" of mappings. + */ +BasicSourceMapConsumer.prototype._findMapping = + function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, + aColumnName, aComparator, aBias) { + // To return the position we are searching for, we must first find the + // mapping for the given position and then return the opposite position it + // points to. Because the mappings are sorted, we can use binary search to + // find the best mapping. + + if (aNeedle[aLineName] <= 0) { + throw new TypeError('Line must be greater than or equal to 1, got ' + + aNeedle[aLineName]); + } + if (aNeedle[aColumnName] < 0) { + throw new TypeError('Column must be greater than or equal to 0, got ' + + aNeedle[aColumnName]); + } + + return binarySearch.search(aNeedle, aMappings, aComparator, aBias); + }; + +/** + * Compute the last column for each generated mapping. The last column is + * inclusive. + */ +BasicSourceMapConsumer.prototype.computeColumnSpans = + function SourceMapConsumer_computeColumnSpans() { + for (var index = 0; index < this._generatedMappings.length; ++index) { + var mapping = this._generatedMappings[index]; + + // Mappings do not contain a field for the last generated columnt. We + // can come up with an optimistic estimate, however, by assuming that + // mappings are contiguous (i.e. given two consecutive mappings, the + // first mapping ends where the second one starts). + if (index + 1 < this._generatedMappings.length) { + var nextMapping = this._generatedMappings[index + 1]; + + if (mapping.generatedLine === nextMapping.generatedLine) { + mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; + continue; + } + } + + // The last mapping for each line spans the entire line. + mapping.lastGeneratedColumn = Infinity; + } + }; + +/** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ +BasicSourceMapConsumer.prototype.originalPositionFor = + function SourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._generatedMappings, + "generatedLine", + "generatedColumn", + util.compareByGeneratedPositionsDeflated, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._generatedMappings[index]; + + if (mapping.generatedLine === needle.generatedLine) { + var source = util.getArg(mapping, 'source', null); + if (source !== null) { + source = this._sources.at(source); + if (this.sourceRoot != null) { + source = util.join(this.sourceRoot, source); + } + } + var name = util.getArg(mapping, 'name', null); + if (name !== null) { + name = this._names.at(name); + } + return { + source: source, + line: util.getArg(mapping, 'originalLine', null), + column: util.getArg(mapping, 'originalColumn', null), + name: name + }; + } + } + + return { + source: null, + line: null, + column: null, + name: null + }; + }; + +/** + * Return true if we have the source content for every source in the source + * map, false otherwise. + */ +BasicSourceMapConsumer.prototype.hasContentsOfAllSources = + function BasicSourceMapConsumer_hasContentsOfAllSources() { + if (!this.sourcesContent) { + return false; + } + return this.sourcesContent.length >= this._sources.size() && + !this.sourcesContent.some(function (sc) { return sc == null; }); + }; + +/** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ +BasicSourceMapConsumer.prototype.sourceContentFor = + function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + if (!this.sourcesContent) { + return null; + } + + if (this.sourceRoot != null) { + aSource = util.relative(this.sourceRoot, aSource); + } + + if (this._sources.has(aSource)) { + return this.sourcesContent[this._sources.indexOf(aSource)]; + } + + var url; + if (this.sourceRoot != null + && (url = util.urlParse(this.sourceRoot))) { + // XXX: file:// URIs and absolute paths lead to unexpected behavior for + // many users. We can help them out when they expect file:// URIs to + // behave like it would if they were running a local HTTP server. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. + var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); + if (url.scheme == "file" + && this._sources.has(fileUriAbsPath)) { + return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] + } + + if ((!url.path || url.path == "/") + && this._sources.has("/" + aSource)) { + return this.sourcesContent[this._sources.indexOf("/" + aSource)]; + } + } + + // This function is used recursively from + // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we + // don't want to throw if we can't find the source - we just want to + // return null, so we provide a flag to exit gracefully. + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + +/** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ +BasicSourceMapConsumer.prototype.generatedPositionFor = + function SourceMapConsumer_generatedPositionFor(aArgs) { + var source = util.getArg(aArgs, 'source'); + if (this.sourceRoot != null) { + source = util.relative(this.sourceRoot, source); + } + if (!this._sources.has(source)) { + return { + line: null, + column: null, + lastColumn: null + }; + } + source = this._sources.indexOf(source); + + var needle = { + source: source, + originalLine: util.getArg(aArgs, 'line'), + originalColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._originalMappings[index]; + + if (mapping.source === needle.source) { + return { + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }; + } + } + + return { + line: null, + column: null, + lastColumn: null + }; + }; + +exports.BasicSourceMapConsumer = BasicSourceMapConsumer; + +/** + * An IndexedSourceMapConsumer instance represents a parsed source map which + * we can query for information. It differs from BasicSourceMapConsumer in + * that it takes "indexed" source maps (i.e. ones with a "sections" field) as + * input. + * + * The only parameter is a raw source map (either as a JSON string, or already + * parsed to an object). According to the spec for indexed source maps, they + * have the following attributes: + * + * - version: Which version of the source map spec this map is following. + * - file: Optional. The generated file this source map is associated with. + * - sections: A list of section definitions. + * + * Each value under the "sections" field has two fields: + * - offset: The offset into the original specified at which this section + * begins to apply, defined as an object with a "line" and "column" + * field. + * - map: A source map definition. This source map could also be indexed, + * but doesn't have to be. + * + * Instead of the "map" field, it's also possible to have a "url" field + * specifying a URL to retrieve a source map from, but that's currently + * unsupported. + * + * Here's an example source map, taken from the source map spec[0], but + * modified to omit a section which uses the "url" field. + * + * { + * version : 3, + * file: "app.js", + * sections: [{ + * offset: {line:100, column:10}, + * map: { + * version : 3, + * file: "section.js", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AAAA,E;;ABCDE;" + * } + * }], + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt + */ +function IndexedSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sections = util.getArg(sourceMap, 'sections'); + + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + this._sources = new ArraySet(); + this._names = new ArraySet(); + + var lastOffset = { + line: -1, + column: 0 + }; + this._sections = sections.map(function (s) { + if (s.url) { + // The url field will require support for asynchronicity. + // See https://github.com/mozilla/source-map/issues/16 + throw new Error('Support for url field in sections not implemented.'); + } + var offset = util.getArg(s, 'offset'); + var offsetLine = util.getArg(offset, 'line'); + var offsetColumn = util.getArg(offset, 'column'); + + if (offsetLine < lastOffset.line || + (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { + throw new Error('Section offsets must be ordered and non-overlapping.'); + } + lastOffset = offset; + + return { + generatedOffset: { + // The offset fields are 0-based, but we use 1-based indices when + // encoding/decoding from VLQ. + generatedLine: offsetLine + 1, + generatedColumn: offsetColumn + 1 + }, + consumer: new SourceMapConsumer(util.getArg(s, 'map')) + } + }); +} + +IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); +IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; + +/** + * The version of the source mapping spec that we are consuming. + */ +IndexedSourceMapConsumer.prototype._version = 3; + +/** + * The list of original sources. + */ +Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { + get: function () { + var sources = []; + for (var i = 0; i < this._sections.length; i++) { + for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { + sources.push(this._sections[i].consumer.sources[j]); + } + } + return sources; + } +}); + +/** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ +IndexedSourceMapConsumer.prototype.originalPositionFor = + function IndexedSourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + // Find the section containing the generated position we're trying to map + // to an original position. + var sectionIndex = binarySearch.search(needle, this._sections, + function(needle, section) { + var cmp = needle.generatedLine - section.generatedOffset.generatedLine; + if (cmp) { + return cmp; + } + + return (needle.generatedColumn - + section.generatedOffset.generatedColumn); + }); + var section = this._sections[sectionIndex]; + + if (!section) { + return { + source: null, + line: null, + column: null, + name: null + }; + } + + return section.consumer.originalPositionFor({ + line: needle.generatedLine - + (section.generatedOffset.generatedLine - 1), + column: needle.generatedColumn - + (section.generatedOffset.generatedLine === needle.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + bias: aArgs.bias + }); + }; + +/** + * Return true if we have the source content for every source in the source + * map, false otherwise. + */ +IndexedSourceMapConsumer.prototype.hasContentsOfAllSources = + function IndexedSourceMapConsumer_hasContentsOfAllSources() { + return this._sections.every(function (s) { + return s.consumer.hasContentsOfAllSources(); + }); + }; + +/** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ +IndexedSourceMapConsumer.prototype.sourceContentFor = + function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + var content = section.consumer.sourceContentFor(aSource, true); + if (content) { + return content; + } + } + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + +/** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ +IndexedSourceMapConsumer.prototype.generatedPositionFor = + function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + // Only consider this section if the requested source is in the list of + // sources of the consumer. + if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) { + continue; + } + var generatedPosition = section.consumer.generatedPositionFor(aArgs); + if (generatedPosition) { + var ret = { + line: generatedPosition.line + + (section.generatedOffset.generatedLine - 1), + column: generatedPosition.column + + (section.generatedOffset.generatedLine === generatedPosition.line + ? section.generatedOffset.generatedColumn - 1 + : 0) + }; + return ret; + } + } + + return { + line: null, + column: null + }; + }; + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +IndexedSourceMapConsumer.prototype._parseMappings = + function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { + this.__generatedMappings = []; + this.__originalMappings = []; + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + var sectionMappings = section.consumer._generatedMappings; + for (var j = 0; j < sectionMappings.length; j++) { + var mapping = sectionMappings[j]; + + var source = section.consumer._sources.at(mapping.source); + if (section.consumer.sourceRoot !== null) { + source = util.join(section.consumer.sourceRoot, source); + } + this._sources.add(source); + source = this._sources.indexOf(source); + + var name = section.consumer._names.at(mapping.name); + this._names.add(name); + name = this._names.indexOf(name); + + // The mappings coming from the consumer for the section have + // generated positions relative to the start of the section, so we + // need to offset them to be relative to the start of the concatenated + // generated file. + var adjustedMapping = { + source: source, + generatedLine: mapping.generatedLine + + (section.generatedOffset.generatedLine - 1), + generatedColumn: mapping.generatedColumn + + (section.generatedOffset.generatedLine === mapping.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: name + }; + + this.__generatedMappings.push(adjustedMapping); + if (typeof adjustedMapping.originalLine === 'number') { + this.__originalMappings.push(adjustedMapping); + } + } + } + + quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated); + quickSort(this.__originalMappings, util.compareByOriginalPositions); + }; + +exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; + +},{"./array-set":474,"./base64-vlq":475,"./binary-search":477,"./quick-sort":479,"./util":483}],481:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var base64VLQ = require('./base64-vlq'); +var util = require('./util'); +var ArraySet = require('./array-set').ArraySet; +var MappingList = require('./mapping-list').MappingList; + +/** + * An instance of the SourceMapGenerator represents a source map which is + * being built incrementally. You may pass an object with the following + * properties: + * + * - file: The filename of the generated source. + * - sourceRoot: A root for all relative URLs in this source map. + */ +function SourceMapGenerator(aArgs) { + if (!aArgs) { + aArgs = {}; + } + this._file = util.getArg(aArgs, 'file', null); + this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); + this._skipValidation = util.getArg(aArgs, 'skipValidation', false); + this._sources = new ArraySet(); + this._names = new ArraySet(); + this._mappings = new MappingList(); + this._sourcesContents = null; +} + +SourceMapGenerator.prototype._version = 3; + +/** + * Creates a new SourceMapGenerator based on a SourceMapConsumer + * + * @param aSourceMapConsumer The SourceMap. + */ +SourceMapGenerator.fromSourceMap = + function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) { + var sourceRoot = aSourceMapConsumer.sourceRoot; + var generator = new SourceMapGenerator({ + file: aSourceMapConsumer.file, + sourceRoot: sourceRoot + }); + aSourceMapConsumer.eachMapping(function (mapping) { + var newMapping = { + generated: { + line: mapping.generatedLine, + column: mapping.generatedColumn + } + }; + + if (mapping.source != null) { + newMapping.source = mapping.source; + if (sourceRoot != null) { + newMapping.source = util.relative(sourceRoot, newMapping.source); + } + + newMapping.original = { + line: mapping.originalLine, + column: mapping.originalColumn + }; + + if (mapping.name != null) { + newMapping.name = mapping.name; + } + } + + generator.addMapping(newMapping); + }); + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content != null) { + generator.setSourceContent(sourceFile, content); + } + }); + return generator; + }; + +/** + * Add a single mapping from original source line and column to the generated + * source's line and column for this source map being created. The mapping + * object should have the following properties: + * + * - generated: An object with the generated line and column positions. + * - original: An object with the original line and column positions. + * - source: The original source file (relative to the sourceRoot). + * - name: An optional original token name for this mapping. + */ +SourceMapGenerator.prototype.addMapping = + function SourceMapGenerator_addMapping(aArgs) { + var generated = util.getArg(aArgs, 'generated'); + var original = util.getArg(aArgs, 'original', null); + var source = util.getArg(aArgs, 'source', null); + var name = util.getArg(aArgs, 'name', null); + + if (!this._skipValidation) { + this._validateMapping(generated, original, source, name); + } + + if (source != null) { + source = String(source); + if (!this._sources.has(source)) { + this._sources.add(source); + } + } + + if (name != null) { + name = String(name); + if (!this._names.has(name)) { + this._names.add(name); + } + } + + this._mappings.add({ + generatedLine: generated.line, + generatedColumn: generated.column, + originalLine: original != null && original.line, + originalColumn: original != null && original.column, + source: source, + name: name + }); + }; + +/** + * Set the source content for a source file. + */ +SourceMapGenerator.prototype.setSourceContent = + function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) { + var source = aSourceFile; + if (this._sourceRoot != null) { + source = util.relative(this._sourceRoot, source); + } + + if (aSourceContent != null) { + // Add the source content to the _sourcesContents map. + // Create a new _sourcesContents map if the property is null. + if (!this._sourcesContents) { + this._sourcesContents = Object.create(null); + } + this._sourcesContents[util.toSetString(source)] = aSourceContent; + } else if (this._sourcesContents) { + // Remove the source file from the _sourcesContents map. + // If the _sourcesContents map is empty, set the property to null. + delete this._sourcesContents[util.toSetString(source)]; + if (Object.keys(this._sourcesContents).length === 0) { + this._sourcesContents = null; + } + } + }; + +/** + * Applies the mappings of a sub-source-map for a specific source file to the + * source map being generated. Each mapping to the supplied source file is + * rewritten using the supplied source map. Note: The resolution for the + * resulting mappings is the minimium of this map and the supplied map. + * + * @param aSourceMapConsumer The source map to be applied. + * @param aSourceFile Optional. The filename of the source file. + * If omitted, SourceMapConsumer's file property will be used. + * @param aSourceMapPath Optional. The dirname of the path to the source map + * to be applied. If relative, it is relative to the SourceMapConsumer. + * This parameter is needed when the two source maps aren't in the same + * directory, and the source map to be applied contains relative source + * paths. If so, those relative source paths need to be rewritten + * relative to the SourceMapGenerator. + */ +SourceMapGenerator.prototype.applySourceMap = + function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) { + var sourceFile = aSourceFile; + // If aSourceFile is omitted, we will use the file property of the SourceMap + if (aSourceFile == null) { + if (aSourceMapConsumer.file == null) { + throw new Error( + 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' + + 'or the source map\'s "file" property. Both were omitted.' + ); + } + sourceFile = aSourceMapConsumer.file; + } + var sourceRoot = this._sourceRoot; + // Make "sourceFile" relative if an absolute Url is passed. + if (sourceRoot != null) { + sourceFile = util.relative(sourceRoot, sourceFile); + } + // Applying the SourceMap can add and remove items from the sources and + // the names array. + var newSources = new ArraySet(); + var newNames = new ArraySet(); + + // Find mappings for the "sourceFile" + this._mappings.unsortedForEach(function (mapping) { + if (mapping.source === sourceFile && mapping.originalLine != null) { + // Check if it can be mapped by the source map, then update the mapping. + var original = aSourceMapConsumer.originalPositionFor({ + line: mapping.originalLine, + column: mapping.originalColumn + }); + if (original.source != null) { + // Copy mapping + mapping.source = original.source; + if (aSourceMapPath != null) { + mapping.source = util.join(aSourceMapPath, mapping.source) + } + if (sourceRoot != null) { + mapping.source = util.relative(sourceRoot, mapping.source); + } + mapping.originalLine = original.line; + mapping.originalColumn = original.column; + if (original.name != null) { + mapping.name = original.name; + } + } + } + + var source = mapping.source; + if (source != null && !newSources.has(source)) { + newSources.add(source); + } + + var name = mapping.name; + if (name != null && !newNames.has(name)) { + newNames.add(name); + } + + }, this); + this._sources = newSources; + this._names = newNames; + + // Copy sourcesContents of applied map. + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content != null) { + if (aSourceMapPath != null) { + sourceFile = util.join(aSourceMapPath, sourceFile); + } + if (sourceRoot != null) { + sourceFile = util.relative(sourceRoot, sourceFile); + } + this.setSourceContent(sourceFile, content); + } + }, this); + }; + +/** + * A mapping can have one of the three levels of data: + * + * 1. Just the generated position. + * 2. The Generated position, original position, and original source. + * 3. Generated and original position, original source, as well as a name + * token. + * + * To maintain consistency, we validate that any new mapping being added falls + * in to one of these categories. + */ +SourceMapGenerator.prototype._validateMapping = + function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource, + aName) { + if (aGenerated && 'line' in aGenerated && 'column' in aGenerated + && aGenerated.line > 0 && aGenerated.column >= 0 + && !aOriginal && !aSource && !aName) { + // Case 1. + return; + } + else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated + && aOriginal && 'line' in aOriginal && 'column' in aOriginal + && aGenerated.line > 0 && aGenerated.column >= 0 + && aOriginal.line > 0 && aOriginal.column >= 0 + && aSource) { + // Cases 2 and 3. + return; + } + else { + throw new Error('Invalid mapping: ' + JSON.stringify({ + generated: aGenerated, + source: aSource, + original: aOriginal, + name: aName + })); + } + }; + +/** + * Serialize the accumulated mappings in to the stream of base 64 VLQs + * specified by the source map format. + */ +SourceMapGenerator.prototype._serializeMappings = + function SourceMapGenerator_serializeMappings() { + var previousGeneratedColumn = 0; + var previousGeneratedLine = 1; + var previousOriginalColumn = 0; + var previousOriginalLine = 0; + var previousName = 0; + var previousSource = 0; + var result = ''; + var next; + var mapping; + var nameIdx; + var sourceIdx; + + var mappings = this._mappings.toArray(); + for (var i = 0, len = mappings.length; i < len; i++) { + mapping = mappings[i]; + next = '' + + if (mapping.generatedLine !== previousGeneratedLine) { + previousGeneratedColumn = 0; + while (mapping.generatedLine !== previousGeneratedLine) { + next += ';'; + previousGeneratedLine++; + } + } + else { + if (i > 0) { + if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) { + continue; + } + next += ','; + } + } + + next += base64VLQ.encode(mapping.generatedColumn + - previousGeneratedColumn); + previousGeneratedColumn = mapping.generatedColumn; + + if (mapping.source != null) { + sourceIdx = this._sources.indexOf(mapping.source); + next += base64VLQ.encode(sourceIdx - previousSource); + previousSource = sourceIdx; + + // lines are stored 0-based in SourceMap spec version 3 + next += base64VLQ.encode(mapping.originalLine - 1 + - previousOriginalLine); + previousOriginalLine = mapping.originalLine - 1; + + next += base64VLQ.encode(mapping.originalColumn + - previousOriginalColumn); + previousOriginalColumn = mapping.originalColumn; + + if (mapping.name != null) { + nameIdx = this._names.indexOf(mapping.name); + next += base64VLQ.encode(nameIdx - previousName); + previousName = nameIdx; + } + } + + result += next; + } + + return result; + }; + +SourceMapGenerator.prototype._generateSourcesContent = + function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) { + return aSources.map(function (source) { + if (!this._sourcesContents) { + return null; + } + if (aSourceRoot != null) { + source = util.relative(aSourceRoot, source); + } + var key = util.toSetString(source); + return Object.prototype.hasOwnProperty.call(this._sourcesContents, key) + ? this._sourcesContents[key] + : null; + }, this); + }; + +/** + * Externalize the source map. + */ +SourceMapGenerator.prototype.toJSON = + function SourceMapGenerator_toJSON() { + var map = { + version: this._version, + sources: this._sources.toArray(), + names: this._names.toArray(), + mappings: this._serializeMappings() + }; + if (this._file != null) { + map.file = this._file; + } + if (this._sourceRoot != null) { + map.sourceRoot = this._sourceRoot; + } + if (this._sourcesContents) { + map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot); + } + + return map; + }; + +/** + * Render the source map being generated to a string. + */ +SourceMapGenerator.prototype.toString = + function SourceMapGenerator_toString() { + return JSON.stringify(this.toJSON()); + }; + +exports.SourceMapGenerator = SourceMapGenerator; + +},{"./array-set":474,"./base64-vlq":475,"./mapping-list":478,"./util":483}],482:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var SourceMapGenerator = require('./source-map-generator').SourceMapGenerator; +var util = require('./util'); + +// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other +// operating systems these days (capturing the result). +var REGEX_NEWLINE = /(\r?\n)/; + +// Newline character code for charCodeAt() comparisons +var NEWLINE_CODE = 10; + +// Private symbol for identifying `SourceNode`s when multiple versions of +// the source-map library are loaded. This MUST NOT CHANGE across +// versions! +var isSourceNode = "$$$isSourceNode$$$"; + +/** + * SourceNodes provide a way to abstract over interpolating/concatenating + * snippets of generated JavaScript source code while maintaining the line and + * column information associated with the original source code. + * + * @param aLine The original line number. + * @param aColumn The original column number. + * @param aSource The original source's filename. + * @param aChunks Optional. An array of strings which are snippets of + * generated JS, or other SourceNodes. + * @param aName The original identifier. + */ +function SourceNode(aLine, aColumn, aSource, aChunks, aName) { + this.children = []; + this.sourceContents = {}; + this.line = aLine == null ? null : aLine; + this.column = aColumn == null ? null : aColumn; + this.source = aSource == null ? null : aSource; + this.name = aName == null ? null : aName; + this[isSourceNode] = true; + if (aChunks != null) this.add(aChunks); +} + +/** + * Creates a SourceNode from generated code and a SourceMapConsumer. + * + * @param aGeneratedCode The generated code + * @param aSourceMapConsumer The SourceMap for the generated code + * @param aRelativePath Optional. The path that relative sources in the + * SourceMapConsumer should be relative to. + */ +SourceNode.fromStringWithSourceMap = + function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) { + // The SourceNode we want to fill with the generated code + // and the SourceMap + var node = new SourceNode(); + + // All even indices of this array are one line of the generated code, + // while all odd indices are the newlines between two adjacent lines + // (since `REGEX_NEWLINE` captures its match). + // Processed fragments are removed from this array, by calling `shiftNextLine`. + var remainingLines = aGeneratedCode.split(REGEX_NEWLINE); + var shiftNextLine = function() { + var lineContents = remainingLines.shift(); + // The last line of a file might not have a newline. + var newLine = remainingLines.shift() || ""; + return lineContents + newLine; + }; + + // We need to remember the position of "remainingLines" + var lastGeneratedLine = 1, lastGeneratedColumn = 0; + + // The generate SourceNodes we need a code range. + // To extract it current and last mapping is used. + // Here we store the last mapping. + var lastMapping = null; + + aSourceMapConsumer.eachMapping(function (mapping) { + if (lastMapping !== null) { + // We add the code from "lastMapping" to "mapping": + // First check if there is a new line in between. + if (lastGeneratedLine < mapping.generatedLine) { + // Associate first line with "lastMapping" + addMappingWithCode(lastMapping, shiftNextLine()); + lastGeneratedLine++; + lastGeneratedColumn = 0; + // The remaining code is added without mapping + } else { + // There is no new line in between. + // Associate the code between "lastGeneratedColumn" and + // "mapping.generatedColumn" with "lastMapping" + var nextLine = remainingLines[0]; + var code = nextLine.substr(0, mapping.generatedColumn - + lastGeneratedColumn); + remainingLines[0] = nextLine.substr(mapping.generatedColumn - + lastGeneratedColumn); + lastGeneratedColumn = mapping.generatedColumn; + addMappingWithCode(lastMapping, code); + // No more remaining code, continue + lastMapping = mapping; + return; + } + } + // We add the generated code until the first mapping + // to the SourceNode without any mapping. + // Each line is added as separate string. + while (lastGeneratedLine < mapping.generatedLine) { + node.add(shiftNextLine()); + lastGeneratedLine++; + } + if (lastGeneratedColumn < mapping.generatedColumn) { + var nextLine = remainingLines[0]; + node.add(nextLine.substr(0, mapping.generatedColumn)); + remainingLines[0] = nextLine.substr(mapping.generatedColumn); + lastGeneratedColumn = mapping.generatedColumn; + } + lastMapping = mapping; + }, this); + // We have processed all mappings. + if (remainingLines.length > 0) { + if (lastMapping) { + // Associate the remaining code in the current line with "lastMapping" + addMappingWithCode(lastMapping, shiftNextLine()); + } + // and add the remaining lines without any mapping + node.add(remainingLines.join("")); + } + + // Copy sourcesContent into SourceNode + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content != null) { + if (aRelativePath != null) { + sourceFile = util.join(aRelativePath, sourceFile); + } + node.setSourceContent(sourceFile, content); + } + }); + + return node; + + function addMappingWithCode(mapping, code) { + if (mapping === null || mapping.source === undefined) { + node.add(code); + } else { + var source = aRelativePath + ? util.join(aRelativePath, mapping.source) + : mapping.source; + node.add(new SourceNode(mapping.originalLine, + mapping.originalColumn, + source, + code, + mapping.name)); + } + } + }; + +/** + * Add a chunk of generated JS to this source node. + * + * @param aChunk A string snippet of generated JS code, another instance of + * SourceNode, or an array where each member is one of those things. + */ +SourceNode.prototype.add = function SourceNode_add(aChunk) { + if (Array.isArray(aChunk)) { + aChunk.forEach(function (chunk) { + this.add(chunk); + }, this); + } + else if (aChunk[isSourceNode] || typeof aChunk === "string") { + if (aChunk) { + this.children.push(aChunk); + } + } + else { + throw new TypeError( + "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk + ); + } + return this; +}; + +/** + * Add a chunk of generated JS to the beginning of this source node. + * + * @param aChunk A string snippet of generated JS code, another instance of + * SourceNode, or an array where each member is one of those things. + */ +SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) { + if (Array.isArray(aChunk)) { + for (var i = aChunk.length-1; i >= 0; i--) { + this.prepend(aChunk[i]); + } + } + else if (aChunk[isSourceNode] || typeof aChunk === "string") { + this.children.unshift(aChunk); + } + else { + throw new TypeError( + "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk + ); + } + return this; +}; + +/** + * Walk over the tree of JS snippets in this node and its children. The + * walking function is called once for each snippet of JS and is passed that + * snippet and the its original associated source's line/column location. + * + * @param aFn The traversal function. + */ +SourceNode.prototype.walk = function SourceNode_walk(aFn) { + var chunk; + for (var i = 0, len = this.children.length; i < len; i++) { + chunk = this.children[i]; + if (chunk[isSourceNode]) { + chunk.walk(aFn); + } + else { + if (chunk !== '') { + aFn(chunk, { source: this.source, + line: this.line, + column: this.column, + name: this.name }); + } + } + } +}; + +/** + * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between + * each of `this.children`. + * + * @param aSep The separator. + */ +SourceNode.prototype.join = function SourceNode_join(aSep) { + var newChildren; + var i; + var len = this.children.length; + if (len > 0) { + newChildren = []; + for (i = 0; i < len-1; i++) { + newChildren.push(this.children[i]); + newChildren.push(aSep); + } + newChildren.push(this.children[i]); + this.children = newChildren; + } + return this; +}; + +/** + * Call String.prototype.replace on the very right-most source snippet. Useful + * for trimming whitespace from the end of a source node, etc. + * + * @param aPattern The pattern to replace. + * @param aReplacement The thing to replace the pattern with. + */ +SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { + var lastChild = this.children[this.children.length - 1]; + if (lastChild[isSourceNode]) { + lastChild.replaceRight(aPattern, aReplacement); + } + else if (typeof lastChild === 'string') { + this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement); + } + else { + this.children.push(''.replace(aPattern, aReplacement)); + } + return this; +}; + +/** + * Set the source content for a source file. This will be added to the SourceMapGenerator + * in the sourcesContent field. + * + * @param aSourceFile The filename of the source file + * @param aSourceContent The content of the source file + */ +SourceNode.prototype.setSourceContent = + function SourceNode_setSourceContent(aSourceFile, aSourceContent) { + this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent; + }; + +/** + * Walk over the tree of SourceNodes. The walking function is called for each + * source file content and is passed the filename and source content. + * + * @param aFn The traversal function. + */ +SourceNode.prototype.walkSourceContents = + function SourceNode_walkSourceContents(aFn) { + for (var i = 0, len = this.children.length; i < len; i++) { + if (this.children[i][isSourceNode]) { + this.children[i].walkSourceContents(aFn); + } + } + + var sources = Object.keys(this.sourceContents); + for (var i = 0, len = sources.length; i < len; i++) { + aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]); + } + }; + +/** + * Return the string representation of this source node. Walks over the tree + * and concatenates all the various snippets together to one string. + */ +SourceNode.prototype.toString = function SourceNode_toString() { + var str = ""; + this.walk(function (chunk) { + str += chunk; + }); + return str; +}; + +/** + * Returns the string representation of this source node along with a source + * map. + */ +SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) { + var generated = { + code: "", + line: 1, + column: 0 + }; + var map = new SourceMapGenerator(aArgs); + var sourceMappingActive = false; + var lastOriginalSource = null; + var lastOriginalLine = null; + var lastOriginalColumn = null; + var lastOriginalName = null; + this.walk(function (chunk, original) { + generated.code += chunk; + if (original.source !== null + && original.line !== null + && original.column !== null) { + if(lastOriginalSource !== original.source + || lastOriginalLine !== original.line + || lastOriginalColumn !== original.column + || lastOriginalName !== original.name) { + map.addMapping({ + source: original.source, + original: { + line: original.line, + column: original.column + }, + generated: { + line: generated.line, + column: generated.column + }, + name: original.name + }); + } + lastOriginalSource = original.source; + lastOriginalLine = original.line; + lastOriginalColumn = original.column; + lastOriginalName = original.name; + sourceMappingActive = true; + } else if (sourceMappingActive) { + map.addMapping({ + generated: { + line: generated.line, + column: generated.column + } + }); + lastOriginalSource = null; + sourceMappingActive = false; + } + for (var idx = 0, length = chunk.length; idx < length; idx++) { + if (chunk.charCodeAt(idx) === NEWLINE_CODE) { + generated.line++; + generated.column = 0; + // Mappings end at eol + if (idx + 1 === length) { + lastOriginalSource = null; + sourceMappingActive = false; + } else if (sourceMappingActive) { + map.addMapping({ + source: original.source, + original: { + line: original.line, + column: original.column + }, + generated: { + line: generated.line, + column: generated.column + }, + name: original.name + }); + } + } else { + generated.column++; + } + } + }); + this.walkSourceContents(function (sourceFile, sourceContent) { + map.setSourceContent(sourceFile, sourceContent); + }); + + return { code: generated.code, map: map }; +}; + +exports.SourceNode = SourceNode; + +},{"./source-map-generator":481,"./util":483}],483:[function(require,module,exports){ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +/** + * This is a helper function for getting values from parameter/options + * objects. + * + * @param args The object we are extracting values from + * @param name The name of the property we are getting. + * @param defaultValue An optional value to return if the property is missing + * from the object. If this is not specified and the property is missing, an + * error will be thrown. + */ +function getArg(aArgs, aName, aDefaultValue) { + if (aName in aArgs) { + return aArgs[aName]; + } else if (arguments.length === 3) { + return aDefaultValue; + } else { + throw new Error('"' + aName + '" is a required argument.'); + } +} +exports.getArg = getArg; + +var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/; +var dataUrlRegexp = /^data:.+\,.+$/; + +function urlParse(aUrl) { + var match = aUrl.match(urlRegexp); + if (!match) { + return null; + } + return { + scheme: match[1], + auth: match[2], + host: match[3], + port: match[4], + path: match[5] + }; +} +exports.urlParse = urlParse; + +function urlGenerate(aParsedUrl) { + var url = ''; + if (aParsedUrl.scheme) { + url += aParsedUrl.scheme + ':'; + } + url += '//'; + if (aParsedUrl.auth) { + url += aParsedUrl.auth + '@'; + } + if (aParsedUrl.host) { + url += aParsedUrl.host; + } + if (aParsedUrl.port) { + url += ":" + aParsedUrl.port + } + if (aParsedUrl.path) { + url += aParsedUrl.path; + } + return url; +} +exports.urlGenerate = urlGenerate; + +/** + * Normalizes a path, or the path portion of a URL: + * + * - Replaces consecutive slashes with one slash. + * - Removes unnecessary '.' parts. + * - Removes unnecessary '/..' parts. + * + * Based on code in the Node.js 'path' core module. + * + * @param aPath The path or url to normalize. + */ +function normalize(aPath) { + var path = aPath; + var url = urlParse(aPath); + if (url) { + if (!url.path) { + return aPath; + } + path = url.path; + } + var isAbsolute = exports.isAbsolute(path); + + var parts = path.split(/\/+/); + for (var part, up = 0, i = parts.length - 1; i >= 0; i--) { + part = parts[i]; + if (part === '.') { + parts.splice(i, 1); + } else if (part === '..') { + up++; + } else if (up > 0) { + if (part === '') { + // The first part is blank if the path is absolute. Trying to go + // above the root is a no-op. Therefore we can remove all '..' parts + // directly after the root. + parts.splice(i + 1, up); + up = 0; + } else { + parts.splice(i, 2); + up--; + } + } + } + path = parts.join('/'); + + if (path === '') { + path = isAbsolute ? '/' : '.'; + } + + if (url) { + url.path = path; + return urlGenerate(url); + } + return path; +} +exports.normalize = normalize; + +/** + * Joins two paths/URLs. + * + * @param aRoot The root path or URL. + * @param aPath The path or URL to be joined with the root. + * + * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a + * scheme-relative URL: Then the scheme of aRoot, if any, is prepended + * first. + * - Otherwise aPath is a path. If aRoot is a URL, then its path portion + * is updated with the result and aRoot is returned. Otherwise the result + * is returned. + * - If aPath is absolute, the result is aPath. + * - Otherwise the two paths are joined with a slash. + * - Joining for example 'http://' and 'www.example.com' is also supported. + */ +function join(aRoot, aPath) { + if (aRoot === "") { + aRoot = "."; + } + if (aPath === "") { + aPath = "."; + } + var aPathUrl = urlParse(aPath); + var aRootUrl = urlParse(aRoot); + if (aRootUrl) { + aRoot = aRootUrl.path || '/'; + } + + // `join(foo, '//www.example.org')` + if (aPathUrl && !aPathUrl.scheme) { + if (aRootUrl) { + aPathUrl.scheme = aRootUrl.scheme; + } + return urlGenerate(aPathUrl); + } + + if (aPathUrl || aPath.match(dataUrlRegexp)) { + return aPath; + } + + // `join('http://', 'www.example.com')` + if (aRootUrl && !aRootUrl.host && !aRootUrl.path) { + aRootUrl.host = aPath; + return urlGenerate(aRootUrl); + } + + var joined = aPath.charAt(0) === '/' + ? aPath + : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath); + + if (aRootUrl) { + aRootUrl.path = joined; + return urlGenerate(aRootUrl); + } + return joined; +} +exports.join = join; + +exports.isAbsolute = function (aPath) { + return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp); +}; + +/** + * Make a path relative to a URL or another path. + * + * @param aRoot The root path or URL. + * @param aPath The path or URL to be made relative to aRoot. + */ +function relative(aRoot, aPath) { + if (aRoot === "") { + aRoot = "."; + } + + aRoot = aRoot.replace(/\/$/, ''); + + // It is possible for the path to be above the root. In this case, simply + // checking whether the root is a prefix of the path won't work. Instead, we + // need to remove components from the root one by one, until either we find + // a prefix that fits, or we run out of components to remove. + var level = 0; + while (aPath.indexOf(aRoot + '/') !== 0) { + var index = aRoot.lastIndexOf("/"); + if (index < 0) { + return aPath; + } + + // If the only part of the root that is left is the scheme (i.e. http://, + // file:///, etc.), one or more slashes (/), or simply nothing at all, we + // have exhausted all components, so the path is not relative to the root. + aRoot = aRoot.slice(0, index); + if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { + return aPath; + } + + ++level; + } + + // Make sure we add a "../" for each component we removed from the root. + return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); +} +exports.relative = relative; + +var supportsNullProto = (function () { + var obj = Object.create(null); + return !('__proto__' in obj); +}()); + +function identity (s) { + return s; +} + +/** + * Because behavior goes wacky when you set `__proto__` on objects, we + * have to prefix all the strings in our set with an arbitrary character. + * + * See https://github.com/mozilla/source-map/pull/31 and + * https://github.com/mozilla/source-map/issues/30 + * + * @param String aStr + */ +function toSetString(aStr) { + if (isProtoString(aStr)) { + return '$' + aStr; + } + + return aStr; +} +exports.toSetString = supportsNullProto ? identity : toSetString; + +function fromSetString(aStr) { + if (isProtoString(aStr)) { + return aStr.slice(1); + } + + return aStr; +} +exports.fromSetString = supportsNullProto ? identity : fromSetString; + +function isProtoString(s) { + if (!s) { + return false; + } + + var length = s.length; + + if (length < 9 /* "__proto__".length */) { + return false; + } + + if (s.charCodeAt(length - 1) !== 95 /* '_' */ || + s.charCodeAt(length - 2) !== 95 /* '_' */ || + s.charCodeAt(length - 3) !== 111 /* 'o' */ || + s.charCodeAt(length - 4) !== 116 /* 't' */ || + s.charCodeAt(length - 5) !== 111 /* 'o' */ || + s.charCodeAt(length - 6) !== 114 /* 'r' */ || + s.charCodeAt(length - 7) !== 112 /* 'p' */ || + s.charCodeAt(length - 8) !== 95 /* '_' */ || + s.charCodeAt(length - 9) !== 95 /* '_' */) { + return false; + } + + for (var i = length - 10; i >= 0; i--) { + if (s.charCodeAt(i) !== 36 /* '$' */) { + return false; + } + } + + return true; +} + +/** + * Comparator between two mappings where the original positions are compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same original source/line/column, but different generated + * line and column the same. Useful when searching for a mapping with a + * stubbed out mapping. + */ +function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { + var cmp = mappingA.source - mappingB.source; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0 || onlyCompareOriginal) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + return mappingA.name - mappingB.name; +} +exports.compareByOriginalPositions = compareByOriginalPositions; + +/** + * Comparator between two mappings with deflated source and name indices where + * the generated positions are compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same generated line and column, but different + * source/name/original line and column the same. Useful when searching for a + * mapping with a stubbed out mapping. + */ +function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { + var cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0 || onlyCompareGenerated) { + return cmp; + } + + cmp = mappingA.source - mappingB.source; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return mappingA.name - mappingB.name; +} +exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated; + +function strcmp(aStr1, aStr2) { + if (aStr1 === aStr2) { + return 0; + } + + if (aStr1 > aStr2) { + return 1; + } + + return -1; +} + +/** + * Comparator between two mappings with inflated source and name strings where + * the generated positions are compared. + */ +function compareByGeneratedPositionsInflated(mappingA, mappingB) { + var cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} +exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated; + +},{}],484:[function(require,module,exports){ +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ +exports.SourceMapGenerator = require('./lib/source-map-generator').SourceMapGenerator; +exports.SourceMapConsumer = require('./lib/source-map-consumer').SourceMapConsumer; +exports.SourceNode = require('./lib/source-node').SourceNode; + +},{"./lib/source-map-consumer":480,"./lib/source-map-generator":481,"./lib/source-node":482}],485:[function(require,module,exports){ +'use strict'; +var ansiRegex = require('ansi-regex')(); + +module.exports = function (str) { + return typeof str === 'string' ? str.replace(ansiRegex, '') : str; +}; + +},{"ansi-regex":1}],486:[function(require,module,exports){ +(function (process){ +'use strict'; +var argv = process.argv; + +var terminator = argv.indexOf('--'); +var hasFlag = function (flag) { + flag = '--' + flag; + var pos = argv.indexOf(flag); + return pos !== -1 && (terminator !== -1 ? pos < terminator : true); +}; + +module.exports = (function () { + if ('FORCE_COLOR' in process.env) { + return true; + } + + if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false')) { + return false; + } + + if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + return true; + } + + if (process.stdout && !process.stdout.isTTY) { + return false; + } + + if (process.platform === 'win32') { + return true; + } + + if ('COLORTERM' in process.env) { + return true; + } + + if (process.env.TERM === 'dumb') { + return false; + } + + if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)) { + return true; + } + + return false; +})(); + +}).call(this,require('_process')) +},{"_process":471}],487:[function(require,module,exports){ +'use strict'; +module.exports = function toFastProperties(obj) { + function f() {} + f.prototype = obj; + new f(); + return; + eval(obj); +}; + +},{}],488:[function(require,module,exports){ +'use strict'; +module.exports = function (str) { + var tail = str.length; + + while (/[\s\uFEFF\u00A0]/.test(str[tail - 1])) { + tail--; + } + + return str.slice(0, tail); +}; + +},{}],489:[function(require,module,exports){ +exports.isatty = function () { return false; }; + +function ReadStream() { + throw new Error('tty.ReadStream is not implemented'); +} +exports.ReadStream = ReadStream; + +function WriteStream() { + throw new Error('tty.ReadStream is not implemented'); +} +exports.WriteStream = WriteStream; + +},{}],490:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],491:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],492:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":491,"_process":471,"inherits":490}],493:[function(require,module,exports){ +/*import { transform as babelTransform } from 'babel-core'; +import babelTransformDynamicImport from 'babel-plugin-syntax-dynamic-import'; +import babelTransformES2015ModulesSystemJS from 'babel-plugin-transform-es2015-modules-systemjs';*/ + +// sadly, due to how rollup works, we can't use es6 imports here +var babelTransform = require('babel-core').transform; +var babelTransformDynamicImport = require('babel-plugin-syntax-dynamic-import'); +var babelTransformES2015ModulesSystemJS = require('babel-plugin-transform-es2015-modules-systemjs'); + +self.onmessage = function (evt) { + // transform source with Babel + var output = babelTransform(evt.data.source, { + compact: false, + filename: evt.data.key + '!transpiled', + sourceFileName: evt.data.key, + moduleIds: false, + sourceMaps: 'inline', + babelrc: false, + plugins: [babelTransformDynamicImport, babelTransformES2015ModulesSystemJS], + }); + + self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source}); +}; + +},{"babel-core":4,"babel-plugin-syntax-dynamic-import":54,"babel-plugin-transform-es2015-modules-systemjs":55}]},{},[493]); diff --git a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js new file mode 100644 index 00000000..3d123da6 --- /dev/null +++ b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js @@ -0,0 +1,1377 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.BrowserESModuleLoader = factory()); +}(this, (function () { 'use strict'; + +/* + * Environment + */ +var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; +var isNode = typeof process !== 'undefined' && process.versions && process.versions.node; +var isWindows = typeof process !== 'undefined' && typeof process.platform === 'string' && process.platform.match(/^win/); + +var envGlobal = typeof self !== 'undefined' ? self : global; +/* + * Simple Symbol() shim + */ +var hasSymbol = typeof Symbol !== 'undefined'; +function createSymbol (name) { + return hasSymbol ? Symbol() : '@@' + name; +} + + + + + +/* + * Environment baseURI + */ +var baseURI; + +// environent baseURI detection +if (typeof document != 'undefined' && document.getElementsByTagName) { + baseURI = document.baseURI; + + if (!baseURI) { + var bases = document.getElementsByTagName('base'); + baseURI = bases[0] && bases[0].href || window.location.href; + } +} +else if (typeof location != 'undefined') { + baseURI = location.href; +} + +// sanitize out the hash and querystring +if (baseURI) { + baseURI = baseURI.split('#')[0].split('?')[0]; + var slashIndex = baseURI.lastIndexOf('/'); + if (slashIndex !== -1) + baseURI = baseURI.substr(0, slashIndex + 1); +} +else if (typeof process !== 'undefined' && process.cwd) { + baseURI = 'file://' + (isWindows ? '/' : '') + process.cwd(); + if (isWindows) + baseURI = baseURI.replace(/\\/g, '/'); +} +else { + throw new TypeError('No environment baseURI'); +} + +// ensure baseURI has trailing "/" +if (baseURI[baseURI.length - 1] !== '/') + baseURI += '/'; + +/* + * LoaderError with chaining for loader stacks + */ +var errArgs = new Error(0, '_').fileName == '_'; +function LoaderError__Check_error_message_for_loader_stack (childErr, newMessage) { + // Convert file:/// URLs to paths in Node + if (!isBrowser) + newMessage = newMessage.replace(isWindows ? /file:\/\/\//g : /file:\/\//g, ''); + + var message = (childErr.message || childErr) + '\n ' + newMessage; + + var err; + if (errArgs && childErr.fileName) + err = new Error(message, childErr.fileName, childErr.lineNumber); + else + err = new Error(message); + + + var stack = childErr.originalErr ? childErr.originalErr.stack : childErr.stack; + + if (isNode) + // node doesn't show the message otherwise + err.stack = message + '\n ' + stack; + else + err.stack = stack; + + err.originalErr = childErr.originalErr || childErr; + + return err; +} + +var resolvedPromise = Promise.resolve(); + +/* + * Simple Array values shim + */ +function arrayValues (arr) { + if (arr.values) + return arr.values(); + + if (typeof Symbol === 'undefined' || !Symbol.iterator) + throw new Error('Symbol.iterator not supported in this browser'); + + var iterable = {}; + iterable[Symbol.iterator] = function () { + var keys = Object.keys(arr); + var keyIndex = 0; + return { + next: function () { + if (keyIndex < keys.length) + return { + value: arr[keys[keyIndex++]], + done: false + }; + else + return { + value: undefined, + done: true + }; + } + }; + }; + return iterable; +} + +/* + * 3. Reflect.Loader + * + * We skip the entire native internal pipeline, just providing the bare API + */ +// 3.1.1 +function Loader () { + this.registry = new Registry(); +} +// 3.3.1 +Loader.prototype.constructor = Loader; + +function ensureInstantiated (module) { + if (!(module instanceof ModuleNamespace)) + throw new TypeError('Module instantiation did not return a valid namespace object.'); + return module; +} + +// 3.3.2 +Loader.prototype.import = function (key, parent) { + if (typeof key !== 'string') + throw new TypeError('Loader import method must be passed a module key string'); + // custom resolveInstantiate combined hook for better perf + var loader = this; + return resolvedPromise + .then(function () { + return loader[RESOLVE_INSTANTIATE](key, parent); + }) + .then(ensureInstantiated) + //.then(Module.evaluate) + .catch(function (err) { + throw LoaderError__Check_error_message_for_loader_stack(err, 'Loading ' + key + (parent ? ' from ' + parent : '')); + }); +}; +// 3.3.3 +var RESOLVE = Loader.resolve = createSymbol('resolve'); + +/* + * Combined resolve / instantiate hook + * + * Not in current reduced spec, but necessary to separate RESOLVE from RESOLVE + INSTANTIATE as described + * in the spec notes of this repo to ensure that loader.resolve doesn't instantiate when not wanted. + * + * We implement RESOLVE_INSTANTIATE as a single hook instead of a separate INSTANTIATE in order to avoid + * the need for double registry lookups as a performance optimization. + */ +var RESOLVE_INSTANTIATE = Loader.resolveInstantiate = createSymbol('resolveInstantiate'); + +// default resolveInstantiate is just to call resolve and then get from the registry +// this provides compatibility for the resolveInstantiate optimization +Loader.prototype[RESOLVE_INSTANTIATE] = function (key, parent) { + var loader = this; + return loader.resolve(key, parent) + .then(function (resolved) { + return loader.registry.get(resolved); + }); +}; + +function ensureResolution (resolvedKey) { + if (resolvedKey === undefined) + throw new RangeError('No resolution found.'); + return resolvedKey; +} + +Loader.prototype.resolve = function (key, parent) { + var loader = this; + return resolvedPromise + .then(function() { + return loader[RESOLVE](key, parent); + }) + .then(ensureResolution) + .catch(function (err) { + throw LoaderError__Check_error_message_for_loader_stack(err, 'Resolving ' + key + (parent ? ' to ' + parent : '')); + }); +}; + +// 3.3.4 (import without evaluate) +// this is not documented because the use of deferred evaluation as in Module.evaluate is not +// documented, as it is not considered a stable feature to be encouraged +// Loader.prototype.load may well be deprecated if this stays disabled +/* Loader.prototype.load = function (key, parent) { + return Promise.resolve(this[RESOLVE_INSTANTIATE](key, parent || this.key)) + .catch(function (err) { + throw addToError(err, 'Loading ' + key + (parent ? ' from ' + parent : '')); + }); +}; */ + +/* + * 4. Registry + * + * Instead of structuring through a Map, just use a dictionary object + * We throw for construction attempts so this doesn't affect the public API + * + * Registry has been adjusted to use Namespace objects over ModuleStatus objects + * as part of simplifying loader API implementation + */ +var iteratorSupport = typeof Symbol !== 'undefined' && Symbol.iterator; +var REGISTRY = createSymbol('registry'); +function Registry() { + this[REGISTRY] = {}; + this._registry = REGISTRY; +} +// 4.4.1 +if (iteratorSupport) { + // 4.4.2 + Registry.prototype[Symbol.iterator] = function () { + return this.entries()[Symbol.iterator](); + }; + + // 4.4.3 + Registry.prototype.entries = function () { + var registry = this[REGISTRY]; + return arrayValues(Object.keys(registry).map(function (key) { + return [key, registry[key]]; + })); + }; +} + +// 4.4.4 +Registry.prototype.keys = function () { + return arrayValues(Object.keys(this[REGISTRY])); +}; +// 4.4.5 +Registry.prototype.values = function () { + var registry = this[REGISTRY]; + return arrayValues(Object.keys(registry).map(function (key) { + return registry[key]; + })); +}; +// 4.4.6 +Registry.prototype.get = function (key) { + return this[REGISTRY][key]; +}; +// 4.4.7 +Registry.prototype.set = function (key, namespace) { + if (!(namespace instanceof ModuleNamespace)) + throw new Error('Registry must be set with an instance of Module Namespace'); + this[REGISTRY][key] = namespace; + return this; +}; +// 4.4.8 +Registry.prototype.has = function (key) { + return Object.hasOwnProperty.call(this[REGISTRY], key); +}; +// 4.4.9 +Registry.prototype.delete = function (key) { + if (Object.hasOwnProperty.call(this[REGISTRY], key)) { + delete this[REGISTRY][key]; + return true; + } + return false; +}; + +/* + * Simple ModuleNamespace Exotic object based on a baseObject + * We export this for allowing a fast-path for module namespace creation over Module descriptors + */ +// var EVALUATE = createSymbol('evaluate'); +var BASE_OBJECT = createSymbol('baseObject'); + +// 8.3.1 Reflect.Module +/* + * Best-effort simplified non-spec implementation based on + * a baseObject referenced via getters. + * + * Allows: + * + * loader.registry.set('x', new Module({ default: 'x' })); + * + * Optional evaluation function provides experimental Module.evaluate + * support for non-executed modules in registry. + */ +function ModuleNamespace (baseObject/*, evaluate*/) { + Object.defineProperty(this, BASE_OBJECT, { + value: baseObject + }); + + // evaluate defers namespace population + /* if (evaluate) { + Object.defineProperty(this, EVALUATE, { + value: evaluate, + configurable: true, + writable: true + }); + } + else { */ + Object.keys(baseObject).forEach(extendNamespace, this); + //} +} +// 8.4.2 +ModuleNamespace.prototype = Object.create(null); + +if (typeof Symbol !== 'undefined' && Symbol.toStringTag) + Object.defineProperty(ModuleNamespace.prototype, Symbol.toStringTag, { + value: 'Module' + }); + +function extendNamespace (key) { + Object.defineProperty(this, key, { + enumerable: true, + get: function () { + return this[BASE_OBJECT][key]; + } + }); +} + +/* function doEvaluate (evaluate, context) { + try { + evaluate.call(context); + } + catch (e) { + return e; + } +} + +// 8.4.1 Module.evaluate... not documented or used because this is potentially unstable +Module.evaluate = function (ns) { + var evaluate = ns[EVALUATE]; + if (evaluate) { + ns[EVALUATE] = undefined; + var err = doEvaluate(evaluate); + if (err) { + // cache the error + ns[EVALUATE] = function () { + throw err; + }; + throw err; + } + Object.keys(ns[BASE_OBJECT]).forEach(extendNamespace, ns); + } + // make chainable + return ns; +}; */ + +/* + * Optimized URL normalization assuming a syntax-valid URL parent + */ +function throwResolveError (relUrl, parentUrl) { + throw new RangeError('Unable to resolve "' + relUrl + '" to ' + parentUrl); +} +function resolveIfNotPlain (relUrl, parentUrl) { + relUrl = relUrl.trim(); + var parentProtocol = parentUrl && parentUrl.substr(0, parentUrl.indexOf(':') + 1); + + var firstChar = relUrl[0]; + var secondChar = relUrl[1]; + + // protocol-relative + if (firstChar === '/' && secondChar === '/') { + if (!parentProtocol) + throwResolveError(relUrl, parentUrl); + return parentProtocol + relUrl; + } + // relative-url + else if (firstChar === '.' && (secondChar === '/' || secondChar === '.' && (relUrl[2] === '/' || relUrl.length === 2) || relUrl.length === 1) + || firstChar === '/') { + var parentIsPlain = !parentProtocol || parentUrl[parentProtocol.length] !== '/'; + + // read pathname from parent if a URL + // pathname taken to be part after leading "/" + var pathname; + if (parentIsPlain) { + // resolving to a plain parent -> skip standard URL prefix, and treat entire parent as pathname + if (parentUrl === undefined) + throwResolveError(relUrl, parentUrl); + pathname = parentUrl; + } + else if (parentUrl[parentProtocol.length + 1] === '/') { + // resolving to a :// so we need to read out the auth and host + if (parentProtocol !== 'file:') { + pathname = parentUrl.substr(parentProtocol.length + 2); + pathname = pathname.substr(pathname.indexOf('/') + 1); + } + else { + pathname = parentUrl.substr(8); + } + } + else { + // resolving to :/ so pathname is the /... part + pathname = parentUrl.substr(parentProtocol.length + 1); + } + + if (firstChar === '/') { + if (parentIsPlain) + throwResolveError(relUrl, parentUrl); + else + return parentUrl.substr(0, parentUrl.length - pathname.length - 1) + relUrl; + } + + // join together and split for removal of .. and . segments + // looping the string instead of anything fancy for perf reasons + // '../../../../../z' resolved to 'x/y' is just 'z' regardless of parentIsPlain + var segmented = pathname.substr(0, pathname.lastIndexOf('/') + 1) + relUrl; + + var output = []; + var segmentIndex = undefined; + + for (var i = 0; i < segmented.length; i++) { + // busy reading a segment - only terminate on '/' + if (segmentIndex !== undefined) { + if (segmented[i] === '/') { + output.push(segmented.substr(segmentIndex, i - segmentIndex + 1)); + segmentIndex = undefined; + } + continue; + } + + // new segment - check if it is relative + if (segmented[i] === '.') { + // ../ segment + if (segmented[i + 1] === '.' && (segmented[i + 2] === '/' || i === segmented.length - 2)) { + output.pop(); + i += 2; + } + // ./ segment + else if (segmented[i + 1] === '/' || i === segmented.length - 1) { + i += 1; + } + else { + // the start of a new segment as below + segmentIndex = i; + continue; + } + + // this is the plain URI backtracking error (../, package:x -> error) + if (parentIsPlain && output.length === 0) + throwResolveError(relUrl, parentUrl); + + // trailing . or .. segment + if (i === segmented.length) + output.push(''); + continue; + } + + // it is the start of a new segment + segmentIndex = i; + } + // finish reading out the last segment + if (segmentIndex !== undefined) + output.push(segmented.substr(segmentIndex, segmented.length - segmentIndex)); + + return parentUrl.substr(0, parentUrl.length - pathname.length) + output.join(''); + } + + // sanitizes and verifies (by returning undefined if not a valid URL-like form) + // Windows filepath compatibility is an added convenience here + var protocolIndex = relUrl.indexOf(':'); + if (protocolIndex !== -1) { + if (isNode) { + // C:\x becomes file:///c:/x (we don't support C|\x) + if (relUrl[1] === ':' && relUrl[2] === '\\' && relUrl[0].match(/[a-z]/i)) + return 'file:///' + relUrl.replace(/\\/g, '/'); + } + return relUrl; + } +} + +/* + * Register Loader + * + * Builds directly on top of loader polyfill to provide: + * - loader.register support + * - hookable higher-level resolve + * - instantiate hook returning a ModuleNamespace or undefined for es module loading + * - loader error behaviour as in HTML and loader specs, clearing failed modules from registration cache synchronously + * - build tracing support by providing a .trace=true and .loads object format + */ + +var REGISTER_INTERNAL = createSymbol('register-internal'); + +function RegisterLoader$1 () { + Loader.call(this); + + var registryDelete = this.registry.delete; + this.registry.delete = function (key) { + var deleted = registryDelete.call(this, key); + + // also delete from register registry if linked + if (records.hasOwnProperty(key) && !records[key].linkRecord) + delete records[key]; + + return deleted; + }; + + var records = {}; + + this[REGISTER_INTERNAL] = { + // last anonymous System.register call + lastRegister: undefined, + // in-flight es module load records + records: records + }; + + // tracing + this.trace = false; +} + +RegisterLoader$1.prototype = Object.create(Loader.prototype); +RegisterLoader$1.prototype.constructor = RegisterLoader$1; + +var INSTANTIATE = RegisterLoader$1.instantiate = createSymbol('instantiate'); + +// default normalize is the WhatWG style normalizer +RegisterLoader$1.prototype[RegisterLoader$1.resolve = Loader.resolve] = function (key, parentKey) { + return resolveIfNotPlain(key, parentKey || baseURI); +}; + +RegisterLoader$1.prototype[INSTANTIATE] = function (key, processAnonRegister) {}; + +// once evaluated, the linkRecord is set to undefined leaving just the other load record properties +// this allows tracking new binding listeners for es modules through importerSetters +// for dynamic modules, the load record is removed entirely. +function createLoadRecord (state, key, registration) { + return state.records[key] = { + key: key, + + // defined System.register cache + registration: registration, + + // module namespace object + module: undefined, + + // es-only + // this sticks around so new module loads can listen to binding changes + // for already-loaded modules by adding themselves to their importerSetters + importerSetters: undefined, + + // in-flight linking record + linkRecord: { + // promise for instantiated + instantiatePromise: undefined, + dependencies: undefined, + execute: undefined, + executingRequire: false, + + // underlying module object bindings + moduleObj: undefined, + + // es only, also indicates if es or not + setters: undefined, + + // promise for instantiated dependencies (dependencyInstantiations populated) + depsInstantiatePromise: undefined, + // will be the array of dependency load record or a module namespace + dependencyInstantiations: undefined, + + // indicates if the load and all its dependencies are instantiated and linked + // but not yet executed + // mostly just a performance shortpath to avoid rechecking the promises above + linked: false, + + error: undefined + // NB optimization and way of ensuring module objects in setters + // indicates setters which should run pre-execution of that dependency + // setters is then just for completely executed module objects + // alternatively we just pass the partially filled module objects as + // arguments into the execute function + // hoisted: undefined + } + }; +} + +RegisterLoader$1.prototype[Loader.resolveInstantiate] = function (key, parentKey) { + var loader = this; + var state = this[REGISTER_INTERNAL]; + var registry = loader.registry[loader.registry._registry]; + + return resolveInstantiate(loader, key, parentKey, registry, state) + .then(function (instantiated) { + if (instantiated instanceof ModuleNamespace) + return instantiated; + + // if already beaten to linked, return + if (instantiated.module) + return instantiated.module; + + // resolveInstantiate always returns a load record with a link record and no module value + if (instantiated.linkRecord.linked) + return ensureEvaluate(loader, instantiated, instantiated.linkRecord, registry, state, undefined); + + return instantiateDeps(loader, instantiated, instantiated.linkRecord, registry, state, [instantiated]) + .then(function () { + return ensureEvaluate(loader, instantiated, instantiated.linkRecord, registry, state, undefined); + }) + .catch(function (err) { + clearLoadErrors(loader, instantiated); + throw err; + }); + }); +}; + +function resolveInstantiate (loader, key, parentKey, registry, state) { + // normalization shortpath for already-normalized key + // could add a plain name filter, but doesn't yet seem necessary for perf + var module = registry[key]; + if (module) + return Promise.resolve(module); + + var load = state.records[key]; + + // already linked but not in main registry is ignored + if (load && !load.module) + return instantiate(loader, load, load.linkRecord, registry, state); + + return loader.resolve(key, parentKey) + .then(function (resolvedKey) { + // main loader registry always takes preference + module = registry[resolvedKey]; + if (module) + return module; + + load = state.records[resolvedKey]; + + // already has a module value but not already in the registry (load.module) + // means it was removed by registry.delete, so we should + // disgard the current load record creating a new one over it + // but keep any existing registration + if (!load || load.module) + load = createLoadRecord(state, resolvedKey, load && load.registration); + + var link = load.linkRecord; + if (!link) + return load; + + return instantiate(loader, load, link, registry, state); + }); +} + +function createProcessAnonRegister (loader, load, state) { + return function () { + var lastRegister = state.lastRegister; + + if (!lastRegister) + return !!load.registration; + + state.lastRegister = undefined; + load.registration = lastRegister; + + return true; + }; +} + +function instantiate (loader, load, link, registry, state) { + return link.instantiatePromise || (link.instantiatePromise = + // if there is already an existing registration, skip running instantiate + (load.registration ? Promise.resolve() : Promise.resolve().then(function () { + state.lastRegister = undefined; + return loader[INSTANTIATE](load.key, loader[INSTANTIATE].length > 1 && createProcessAnonRegister(loader, load, state)); + })) + .then(function (instantiation) { + // direct module return from instantiate -> we're done + if (instantiation !== undefined) { + if (!(instantiation instanceof ModuleNamespace)) + throw new TypeError('Instantiate did not return a valid Module object.'); + + delete state.records[load.key]; + if (loader.trace) + traceLoad(loader, load, link); + return registry[load.key] = instantiation; + } + + // run the cached loader.register declaration if there is one + var registration = load.registration; + // clear to allow new registrations for future loads (combined with registry delete) + load.registration = undefined; + if (!registration) + throw new TypeError('Module instantiation did not call an anonymous or correctly named System.register.'); + + link.dependencies = registration[0]; + + load.importerSetters = []; + + link.moduleObj = {}; + + // process System.registerDynamic declaration + if (registration[2]) { + link.moduleObj.default = {}; + link.moduleObj.__useDefault = true; + link.executingRequire = registration[1]; + link.execute = registration[2]; + } + + // process System.register declaration + else { + registerDeclarative(loader, load, link, registration[1]); + } + + // shortpath to instantiateDeps + if (!link.dependencies.length) { + link.linked = true; + if (loader.trace) + traceLoad(loader, load, link); + } + + return load; + }) + .catch(function (err) { + throw link.error = LoaderError__Check_error_message_for_loader_stack(err, 'Instantiating ' + load.key); + })); +} + +// like resolveInstantiate, but returning load records for linking +function resolveInstantiateDep (loader, key, parentKey, registry, state, traceDepMap) { + // normalization shortpaths for already-normalized key + // DISABLED to prioritise consistent resolver calls + // could add a plain name filter, but doesn't yet seem necessary for perf + /* var load = state.records[key]; + var module = registry[key]; + + if (module) { + if (traceDepMap) + traceDepMap[key] = key; + + // registry authority check in case module was deleted or replaced in main registry + if (load && load.module && load.module === module) + return load; + else + return module; + } + + // already linked but not in main registry is ignored + if (load && !load.module) { + if (traceDepMap) + traceDepMap[key] = key; + return instantiate(loader, load, load.linkRecord, registry, state); + } */ + return loader.resolve(key, parentKey) + .then(function (resolvedKey) { + if (traceDepMap) + traceDepMap[key] = resolvedKey; + + // normalization shortpaths for already-normalized key + var load = state.records[resolvedKey]; + var module = registry[resolvedKey]; + + // main loader registry always takes preference + if (module && (!load || load.module && module !== load.module)) + return module; + + // already has a module value but not already in the registry (load.module) + // means it was removed by registry.delete, so we should + // disgard the current load record creating a new one over it + // but keep any existing registration + if (!load || !module && load.module) + load = createLoadRecord(state, resolvedKey, load && load.registration); + + var link = load.linkRecord; + if (!link) + return load; + + return instantiate(loader, load, link, registry, state); + }); +} + +function traceLoad (loader, load, link) { + loader.loads = loader.loads || {}; + loader.loads[load.key] = { + key: load.key, + deps: link.dependencies, + depMap: link.depMap || {} + }; +} + +/* + * Convert a CJS module.exports into a valid object for new Module: + * + * new Module(getEsModule(module.exports)) + * + * Sets the default value to the module, while also reading off named exports carefully. + */ +function registerDeclarative (loader, load, link, declare) { + var moduleObj = link.moduleObj; + var importerSetters = load.importerSetters; + + var locked = false; + + // closure especially not based on link to allow link record disposal + var declared = declare.call(envGlobal, function (name, value) { + // export setter propogation with locking to avoid cycles + if (locked) + return; + + if (typeof name === 'object') { + for (var p in name) + if (p !== '__useDefault') + moduleObj[p] = name[p]; + } + else { + moduleObj[name] = value; + } + + locked = true; + for (var i = 0; i < importerSetters.length; i++) + importerSetters[i](moduleObj); + locked = false; + + return value; + }, new ContextualLoader(loader, load.key)); + + link.setters = declared.setters; + link.execute = declared.execute; + if (declared.exports) + link.moduleObj = moduleObj = declared.exports; +} + +function instantiateDeps (loader, load, link, registry, state, seen) { + return (link.depsInstantiatePromise || (link.depsInstantiatePromise = Promise.resolve() + .then(function () { + var depsInstantiatePromises = Array(link.dependencies.length); + + for (var i = 0; i < link.dependencies.length; i++) + depsInstantiatePromises[i] = resolveInstantiateDep(loader, link.dependencies[i], load.key, registry, state, loader.trace && link.depMap || (link.depMap = {})); + + return Promise.all(depsInstantiatePromises); + }) + .then(function (dependencyInstantiations) { + link.dependencyInstantiations = dependencyInstantiations; + + // run setters to set up bindings to instantiated dependencies + if (link.setters) { + for (var i = 0; i < dependencyInstantiations.length; i++) { + var setter = link.setters[i]; + if (setter) { + var instantiation = dependencyInstantiations[i]; + + if (instantiation instanceof ModuleNamespace) { + setter(instantiation); + } + else { + setter(instantiation.module || instantiation.linkRecord.moduleObj); + // this applies to both es and dynamic registrations + if (instantiation.importerSetters) + instantiation.importerSetters.push(setter); + } + } + } + } + }))) + .then(function () { + // now deeply instantiateDeps on each dependencyInstantiation that is a load record + var deepDepsInstantiatePromises = []; + + for (var i = 0; i < link.dependencies.length; i++) { + var depLoad = link.dependencyInstantiations[i]; + var depLink = depLoad.linkRecord; + + if (!depLink || depLink.linked) + continue; + + if (seen.indexOf(depLoad) !== -1) + continue; + seen.push(depLoad); + + deepDepsInstantiatePromises.push(instantiateDeps(loader, depLoad, depLoad.linkRecord, registry, state, seen)); + } + + return Promise.all(deepDepsInstantiatePromises); + }) + .then(function () { + // as soon as all dependencies instantiated, we are ready for evaluation so can add to the registry + // this can run multiple times, but so what + link.linked = true; + if (loader.trace) + traceLoad(loader, load, link); + + return load; + }) + .catch(function (err) { + err = LoaderError__Check_error_message_for_loader_stack(err, 'Loading ' + load.key); + + // throw up the instantiateDeps stack + // loads are then synchonously cleared at the top-level through the clearLoadErrors helper below + // this then ensures avoiding partially unloaded tree states + link.error = link.error || err; + + throw err; + }); +} + +// clears an errored load and all its errored dependencies from the loads registry +function clearLoadErrors (loader, load) { + var state = loader[REGISTER_INTERNAL]; + + // clear from loads + if (state.records[load.key] === load) + delete state.records[load.key]; + + var link = load.linkRecord; + + if (!link) + return; + + if (link.dependencyInstantiations) + link.dependencyInstantiations.forEach(function (depLoad, index) { + if (!depLoad || depLoad instanceof ModuleNamespace) + return; + + if (depLoad.linkRecord) { + if (depLoad.linkRecord.error) { + // provides a circular reference check + if (state.records[depLoad.key] === depLoad) + clearLoadErrors(loader, depLoad); + } + + // unregister setters for es dependency load records that will remain + if (link.setters && depLoad.importerSetters) { + var setterIndex = depLoad.importerSetters.indexOf(link.setters[index]); + depLoad.importerSetters.splice(setterIndex, 1); + } + } + }); +} + +/* + * System.register + */ +RegisterLoader$1.prototype.register = function (key, deps, declare) { + var state = this[REGISTER_INTERNAL]; + + // anonymous modules get stored as lastAnon + if (declare === undefined) { + state.lastRegister = [key, deps, undefined]; + } + + // everything else registers into the register cache + else { + var load = state.records[key] || createLoadRecord(state, key, undefined); + load.registration = [deps, declare, undefined]; + } +}; + +/* + * System.registerDyanmic + */ +RegisterLoader$1.prototype.registerDynamic = function (key, deps, executingRequire, execute) { + var state = this[REGISTER_INTERNAL]; + + // anonymous modules get stored as lastAnon + if (typeof key !== 'string') { + state.lastRegister = [key, deps, executingRequire]; + } + + // everything else registers into the register cache + else { + var load = state.records[key] || createLoadRecord(state, key, undefined); + load.registration = [deps, executingRequire, execute]; + } +}; + +// ContextualLoader class +// backwards-compatible with previous System.register context argument by exposing .id +function ContextualLoader (loader, key) { + this.loader = loader; + this.key = this.id = key; +} +ContextualLoader.prototype.constructor = function () { + throw new TypeError('Cannot subclass the contextual loader only Reflect.Loader.'); +}; +ContextualLoader.prototype.import = function (key) { + return this.loader.import(key, this.key); +}; +ContextualLoader.prototype.resolve = function (key) { + return this.loader.resolve(key, this.key); +}; +ContextualLoader.prototype.load = function (key) { + return this.loader.load(key, this.key); +}; + +// this is the execution function bound to the Module namespace record +function ensureEvaluate (loader, load, link, registry, state, seen) { + if (load.module) + return load.module; + + if (link.error) + throw link.error; + + if (seen && seen.indexOf(load) !== -1) + return load.linkRecord.moduleObj; + + // for ES loads we always run ensureEvaluate on top-level, so empty seen is passed regardless + // for dynamic loads, we pass seen if also dynamic + var err = doEvaluate(loader, load, link, registry, state, link.setters ? [] : seen || []); + if (err) { + clearLoadErrors(loader, load); + throw err; + } + + return load.module; +} + +function makeDynamicRequire (loader, key, dependencies, dependencyInstantiations, registry, state, seen) { + // we can only require from already-known dependencies + return function (name) { + for (var i = 0; i < dependencies.length; i++) { + if (dependencies[i] === name) { + var depLoad = dependencyInstantiations[i]; + var module; + + if (depLoad instanceof ModuleNamespace) + module = depLoad; + else + module = ensureEvaluate(loader, depLoad, depLoad.linkRecord, registry, state, seen); + + return module.__useDefault ? module.default : module; + } + } + throw new Error('Module ' + name + ' not declared as a System.registerDynamic dependency of ' + key); + }; +} + +// ensures the given es load is evaluated +// returns the error if any +function doEvaluate (loader, load, link, registry, state, seen) { + seen.push(load); + + var err; + + // es modules evaluate dependencies first + // non es modules explicitly call moduleEvaluate through require + if (link.setters) { + var depLoad, depLink; + for (var i = 0; i < link.dependencies.length; i++) { + depLoad = link.dependencyInstantiations[i]; + + if (depLoad instanceof ModuleNamespace) + continue; + + // custom Module returned from instantiate + depLink = depLoad.linkRecord; + if (depLink && seen.indexOf(depLoad) === -1) { + if (depLink.error) + err = depLink.error; + else + // dynamic / declarative boundaries clear the "seen" list + // we just let cross format circular throw as would happen in real implementations + err = doEvaluate(loader, depLoad, depLink, registry, state, depLink.setters ? seen : []); + } + + if (err) + return link.error = LoaderError__Check_error_message_for_loader_stack(err, 'Evaluating ' + load.key); + } + } + + // link.execute won't exist for Module returns from instantiate on top-level load + if (link.execute) { + // ES System.register execute + // "this" is null in ES + if (link.setters) { + err = declarativeExecute(link.execute); + } + // System.registerDynamic execute + // "this" is "exports" in CJS + else { + var module = { id: load.key }; + var moduleObj = link.moduleObj; + Object.defineProperty(module, 'exports', { + configurable: true, + set: function (exports) { + moduleObj.default = exports; + }, + get: function () { + return moduleObj.default; + } + }); + + var require = makeDynamicRequire(loader, load.key, link.dependencies, link.dependencyInstantiations, registry, state, seen); + + // evaluate deps first + if (!link.executingRequire) + for (var i = 0; i < link.dependencies.length; i++) + require(link.dependencies[i]); + + err = dynamicExecute(link.execute, require, moduleObj.default, module); + + // pick up defineProperty calls to module.exports when we can + if (module.exports !== moduleObj.default) + moduleObj.default = module.exports; + + var moduleDefault = moduleObj.default; + + // __esModule flag extension support via lifting + if (moduleDefault && moduleDefault.__esModule) { + if (moduleObj.__useDefault) + delete moduleObj.__useDefault; + for (var p in moduleDefault) { + if (Object.hasOwnProperty.call(moduleDefault, p)) + moduleObj[p] = moduleDefault[p]; + } + moduleObj.__esModule = true; + } + } + } + + if (err) + return link.error = LoaderError__Check_error_message_for_loader_stack(err, 'Evaluating ' + load.key); + + registry[load.key] = load.module = new ModuleNamespace(link.moduleObj); + + // if not an esm module, run importer setters and clear them + // this allows dynamic modules to update themselves into es modules + // as soon as execution has completed + if (!link.setters) { + if (load.importerSetters) + for (var i = 0; i < load.importerSetters.length; i++) + load.importerSetters[i](load.module); + load.importerSetters = undefined; + } + + // dispose link record + load.linkRecord = undefined; +} + +// {} is the closest we can get to call(undefined) +var nullContext = {}; +if (Object.freeze) + Object.freeze(nullContext); + +function declarativeExecute (execute) { + try { + execute.call(nullContext); + } + catch (e) { + return e; + } +} + +function dynamicExecute (execute, require, exports, module) { + try { + var output = execute.call(envGlobal, require, exports, module); + if (output !== undefined) + module.exports = output; + } + catch (e) { + return e; + } +} + +var loader; + +// - + From c53af3f5f66c7d1dcec43952c73b0ecfc8545258 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 14 Feb 2017 18:11:55 -0500 Subject: [PATCH 13/19] Move scripts in vnc.html head This commit moves the vnc.html scripts into head. --- vnc.html | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/vnc.html b/vnc.html index 6ec10a26..b4f43e73 100644 --- a/vnc.html +++ b/vnc.html @@ -59,6 +59,10 @@ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'> --> + + + + @@ -320,11 +324,5 @@ - - - - - - From b4ff032e201fde7a3ebd7a48968fdc268c09d230 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Sun, 26 Feb 2017 16:50:54 -0500 Subject: [PATCH 14/19] Fix view_only clipboard handling We need to actually consume the clipboard event message, even if we don't act on it. --- core/rfb.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/rfb.js b/core/rfb.js index 55ca2275..669c817e 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1188,7 +1188,6 @@ RFB.prototype = { _handle_server_cut_text: function () { Log.Debug("ServerCutText"); - if (this._view_only) { return true; } if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; } this._sock.rQskipBytes(3); // Padding @@ -1196,6 +1195,9 @@ RFB.prototype = { if (this._sock.rQwait("ServerCutText", length, 8)) { return false; } var text = this._sock.rQshiftStr(length); + + if (this._view_only) { return true; } + this._onClipboard(this, text); return true; From e6a8eb15ca05823c17f0414f4a6a0fcff33c7879 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Sun, 26 Feb 2017 16:55:24 -0500 Subject: [PATCH 15/19] Only apply settings with an RFB object We should only apply settings if we have an RFB object available. Some of the settings handlers didn't respect this, but do now. --- app/ui.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/ui.js b/app/ui.js index d72f34dd..d59204c8 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1685,10 +1685,12 @@ const UI = { }, updateLocalCursor: function() { + if (!UI.rfb) return; UI.rfb.set_local_cursor(UI.getSetting('cursor')); }, updateViewOnly: function() { + if (!UI.rfb) return; UI.rfb.set_view_only(UI.getSetting('view_only')); }, From adfc9d3f548b8d3c7aac837bd53c68b16b14b137 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 28 Feb 2017 20:47:02 -0500 Subject: [PATCH 16/19] Move error handler into separate file This commit moves the global error handler into a separate file, so that it can catch module loading errors. This also adds support for properly displaying error messages with newlines in them (since the module loader may throw those) --- app/error-handler.js | 61 +++++++++++++++++++ app/styles/base.css | 5 ++ app/ui.js | 43 +------------ utils/use_require.js | 26 +++++++- .../dist/browser-es-module-loader.js | 34 ++++++++++- .../src/browser-es-module-loader.js | 34 ++++++++++- vnc.html | 3 + 7 files changed, 157 insertions(+), 49 deletions(-) create mode 100644 app/error-handler.js diff --git a/app/error-handler.js b/app/error-handler.js new file mode 100644 index 00000000..30f77068 --- /dev/null +++ b/app/error-handler.js @@ -0,0 +1,61 @@ +// NB: this should *not* be included as a module until we have +// native support in the browsers, so that our error handler +// can catch script-loading errors. + + +(function(){ + "use strict"; + + function convertNewlines(msg, parentElem) { + const lines = msg.split("\n"); + lines.forEach(function (line) { + parentElem.appendChild(document.createElement("br")); + parentElem.appendChild(document.createTextNode(line)); + }); + parentElem.removeChild(parentElem.firstChild); + return parentElem; + } + + // Fallback for all uncought errors + function handleError (event, err) { + try { + const msg = document.getElementById('noVNC_fallback_errormsg'); + + // Only show the initial error + if (msg.hasChildNodes()) { + return false; + } + + var div = document.createElement("div"); + div.classList.add('noVNC_message'); + convertNewlines(event.message, div); + msg.appendChild(div); + + if (event.filename !== undefined && event.lineno !== undefined && event.colno !== undefined) { + div = document.createElement("div"); + div.className = 'noVNC_location'; + const text = event.filename + ":" + event.lineno + ":" + event.colno; + div.appendChild(document.createTextNode(text)); + msg.appendChild(div); + } + + if ((err !== undefined) && + (err.stack !== undefined)) { + div = document.createElement("div"); + div.className = 'noVNC_stack'; + div.appendChild(document.createTextNode(err.stack)); + msg.appendChild(div); + } + + document.getElementById('noVNC_fallback_error') + .classList.add("noVNC_open"); + } catch (exc) { + document.write("noVNC encountered an error."); + } + // Don't return true since this would prevent the error + // from being printed to the browser console. + return false; + } + window.addEventListener('error', function (evt) { handleError(evt, evt.error); }); + window.addEventListener('unhandledrejection', function (evt) { handleError(evt.reason, evt.reason); }); +})(); diff --git a/app/styles/base.css b/app/styles/base.css index 4cec1735..30146963 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -247,6 +247,11 @@ select:active { font-weight: normal; } +#noVNC_fallback_errormsg .noVNC_message { + display: inline-block; + text-align: left; +} + #noVNC_fallback_error .noVNC_location { font-style: italic; font-size: 0.8em; diff --git a/app/ui.js b/app/ui.js index d59204c8..46826221 100644 --- a/app/ui.js +++ b/app/ui.js @@ -21,46 +21,6 @@ import RFB from "../core/rfb.js"; import Display from "../core/display.js"; import * as WebUtil from "./webutil.js"; -// Fallback for all uncought errors -window.addEventListener('error', function(event) { - try { - var msg, div, text; - - msg = document.getElementById('noVNC_fallback_errormsg'); - - // Only show the initial error - if (msg.hasChildNodes()) { - return false; - } - - div = document.createElement("div"); - div.appendChild(document.createTextNode(event.message)); - msg.appendChild(div); - - div = document.createElement("div"); - div.className = 'noVNC_location'; - text = event.filename + ":" + event.lineno + ":" + event.colno; - div.appendChild(document.createTextNode(text)); - msg.appendChild(div); - - if ((event.error !== undefined) && - (event.error.stack !== undefined)) { - div = document.createElement("div"); - div.className = 'noVNC_stack'; - div.appendChild(document.createTextNode(event.error.stack)); - msg.appendChild(div); - } - - document.getElementById('noVNC_fallback_error') - .classList.add("noVNC_open"); - } catch (exc) { - document.write("noVNC encountered an error."); - } - // Don't return true since this would prevent the error - // from being printed to the browser console. - return false; -}); - const UI = { connected: false, @@ -1751,11 +1711,10 @@ if (l10n.language !== "en" && l10n.dictionary === undefined) { // wait for translations to load before loading the UI UI.prime(); }, function (err) { - throw err; + throw err; }); } else { UI.prime(); } - export default UI; diff --git a/utils/use_require.js b/utils/use_require.js index bfa14f6f..f0383d09 100755 --- a/utils/use_require.js +++ b/utils/use_require.js @@ -21,6 +21,17 @@ var vendor_path = path.resolve(__dirname, '..', 'vendor'); var out_dir_base = path.resolve(__dirname, '..', 'build'); var lib_dir_base = path.resolve(__dirname, '..', 'lib'); +const no_copy_files = new Set([ + // skip these -- they don't belong in the processed application + path.join(vendor_path, 'sinon.js'), + path.join(vendor_path, 'browser-es-module-loader'), +]); + +const no_transform_files = new Set([ + // don't transform this -- we want it imported as-is to properly catch loading errors + path.join(app_path, 'error-handler.js'), +]); + // walkDir *recursively* walks directories trees, // calling the callback for all normal files found. var walkDir = function (base_path, cb, filter) { @@ -93,6 +104,8 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) { const helper = helpers[import_format]; var handleDir = (js_only, in_path_base, filename) => { + if (no_copy_files.has(filename)) return; + const out_path = path.join(out_path_base, path.relative(in_path_base, filename)); if(path.extname(filename) !== '.js') { if (!js_only) { @@ -103,10 +116,17 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) { } fse.ensureDir(path.dirname(out_path), () => { + if (no_transform_files.has(filename)) { + console.log(`Writing ${out_path}`); + fse.copy(filename, out_path, (err) => { if (err) throw err; }); + return; + } + const opts = babel_opts(); if (helper && helpers.optionsOverride) { helper.optionsOverride(opts); } + babel.transformFile(filename, babel_opts(), (err, res) => { console.log(`Writing ${out_path}`); if (err) throw err; @@ -124,11 +144,11 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) { }); }; - walkDir(core_path, handleDir.bind(null, true, in_path || core_path)); - walkDir(vendor_path, handleDir.bind(null, true, in_path || main_path), (filepath, stats) => !((stats.isDirectory() && path.basename(filepath) === 'browser-es-module-loader') || path.basename(filepath) === 'sinon.js')); + walkDir(core_path, handleDir.bind(null, true, in_path || core_path), (filename, stats) => !no_copy_files.has(filename)); + walkDir(vendor_path, handleDir.bind(null, true, in_path || main_path), (filename, stats) => !no_copy_files.has(filename)); if (with_app_dir) { - walkDir(app_path, handleDir.bind(null, false, in_path || app_path)); + walkDir(app_path, handleDir.bind(null, false, in_path || app_path), (filename, stats) => !no_copy_files.has(filename)); const out_app_path = path.join(out_path_base, 'app.js'); if (helper && helper.appWriter) { diff --git a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js index 3d123da6..0d22bab2 100644 --- a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js @@ -1180,7 +1180,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) { if (script.type == 'module' && !script.loaded) { script.loaded = true; if (script.src) { - loader.import(script.src); + loader.import(script.src).catch(function(err) { + // dispatch an error event so that we can display in errors in browsers + // that don't yet support unhandledrejection + try { + var evt = new Event('error'); + } catch (_eventError) { + var evt = document.createEvent('Event'); + evt.initEvent('error', true, true); + } + evt.message = err.message; + evt.error = err; + window.dispatchEvent(evt); + + // throw so it still shows up in the console + throw err; + }); } // anonymous modules supported via a custom naming scheme and registry else { @@ -1191,7 +1206,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) { var anonName = resolveIfNotPlain(uri, baseURI); anonSources[anonName] = script.innerHTML; - loader.import(anonName); + loader.import(anonName).catch(function(err) { + // dispatch an error event so that we can display in errors in browsers + // that don't yet support unhandledrejection + try { + var evt = new Event('error'); + } catch (_eventError) { + var evt = document.createEvent('Event'); + evt.initEvent('error', true, true); + } + evt.message = err.message; + evt.error = err; + window.dispatchEvent(evt); + + // throw so it still shows up in the console + throw err; + }); } } } diff --git a/vendor/browser-es-module-loader/src/browser-es-module-loader.js b/vendor/browser-es-module-loader/src/browser-es-module-loader.js index 9ba7c36e..a9d72097 100644 --- a/vendor/browser-es-module-loader/src/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/src/browser-es-module-loader.js @@ -20,7 +20,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) { if (script.type == 'module' && !script.loaded) { script.loaded = true; if (script.src) { - loader.import(script.src); + loader.import(script.src).catch(function(err) { + // dispatch an error event so that we can display in errors in browsers + // that don't yet support unhandledrejection + try { + var evt = new Event('error'); + } catch (_eventError) { + var evt = document.createEvent('Event'); + evt.initEvent('error', true, true); + } + evt.message = err.message; + evt.error = err; + window.dispatchEvent(evt); + + // throw so it still shows up in the console + throw err; + }); } // anonymous modules supported via a custom naming scheme and registry else { @@ -31,7 +46,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) { var anonName = resolveIfNotPlain(uri, baseURI); anonSources[anonName] = script.innerHTML; - loader.import(anonName); + loader.import(anonName).catch(function(err) { + // dispatch an error event so that we can display in errors in browsers + // that don't yet support unhandledrejection + try { + var evt = new Event('error'); + } catch (_eventError) { + var evt = document.createEvent('Event'); + evt.initEvent('error', true, true); + } + evt.message = err.message; + evt.error = err; + window.dispatchEvent(evt); + + // throw so it still shows up in the console + throw err; + }); } } } diff --git a/vnc.html b/vnc.html index b4f43e73..10cc1430 100644 --- a/vnc.html +++ b/vnc.html @@ -59,6 +59,9 @@ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'> --> + + + From 152c399513e250aecda206ce38e2cf934d918c6c Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 1 Mar 2017 11:17:44 -0500 Subject: [PATCH 17/19] Vendor in an IE11 polyfill for Promises This commit introduces a polyfill to add support for Promises in IE11. This means IE11 can be tested without first running `utils/as_require.js`. --- LICENSE.txt | 2 + utils/use_require.js | 41 +-- utils/use_require_helpers.js | 8 +- .../dist/browser-es-module-loader.js | 4 +- .../src/browser-es-module-loader.js | 4 +- vendor/promise.js | 255 ++++++++++++++++++ vnc.html | 2 + 7 files changed, 295 insertions(+), 21 deletions(-) create mode 100644 vendor/promise.js diff --git a/LICENSE.txt b/LICENSE.txt index e9a3e15e..a8d24e39 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -43,6 +43,8 @@ licenses (all MPL 2.0 compatible): vendor/browser-es-module-loader: MIT + vendor/promise.js : MIT + Any other files not mentioned above are typically marked with a copyright/license header at the top of the file. The default noVNC license is MPL-2.0. diff --git a/utils/use_require.js b/utils/use_require.js index f0383d09..8522256b 100755 --- a/utils/use_require.js +++ b/utils/use_require.js @@ -14,24 +14,29 @@ program .parse(process.argv); // the various important paths -var main_path = path.resolve(__dirname, '..'); -var core_path = path.resolve(__dirname, '..', 'core'); -var app_path = path.resolve(__dirname, '..', 'app'); -var vendor_path = path.resolve(__dirname, '..', 'vendor'); -var out_dir_base = path.resolve(__dirname, '..', 'build'); -var lib_dir_base = path.resolve(__dirname, '..', 'lib'); +const paths = { + main: path.resolve(__dirname, '..'), + core: path.resolve(__dirname, '..', 'core'), + app: path.resolve(__dirname, '..', 'app'), + vendor: path.resolve(__dirname, '..', 'vendor'), + out_dir_base: path.resolve(__dirname, '..', 'build'), + lib_dir_base: path.resolve(__dirname, '..', 'lib'), +}; const no_copy_files = new Set([ // skip these -- they don't belong in the processed application - path.join(vendor_path, 'sinon.js'), - path.join(vendor_path, 'browser-es-module-loader'), + path.join(paths.vendor, 'sinon.js'), + path.join(paths.vendor, 'browser-es-module-loader'), + path.join(paths.vendor, 'promise.js'), ]); const no_transform_files = new Set([ // don't transform this -- we want it imported as-is to properly catch loading errors - path.join(app_path, 'error-handler.js'), + path.join(paths.app, 'error-handler.js'), ]); +no_copy_files.forEach((file) => no_transform_files.add(file)); + // walkDir *recursively* walks directories trees, // calling the callback for all normal files found. var walkDir = function (base_path, cb, filter) { @@ -55,7 +60,7 @@ var walkDir = function (base_path, cb, filter) { var transform_html = function (new_script) { // write out the modified vnc.html file that works with the bundle var src_html_path = path.resolve(__dirname, '..', 'vnc.html'); - var out_html_path = path.resolve(out_dir_base, 'vnc.html'); + var out_html_path = path.resolve(paths.out_dir_base, 'vnc.html'); fs.readFile(src_html_path, (err, contents_raw) => { if (err) { throw err; } @@ -92,10 +97,10 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) { var in_path; if (with_app_dir) { - var out_path_base = out_dir_base; - in_path = main_path; + var out_path_base = paths.out_dir_base; + in_path = paths.main; } else { - var out_path_base = lib_dir_base; + var out_path_base = paths.lib_dir_base; } fse.ensureDirSync(out_path_base); @@ -144,11 +149,15 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) { }); }; - walkDir(core_path, handleDir.bind(null, true, in_path || core_path), (filename, stats) => !no_copy_files.has(filename)); - walkDir(vendor_path, handleDir.bind(null, true, in_path || main_path), (filename, stats) => !no_copy_files.has(filename)); + if (with_app_dir && helper && helper.noCopyOverride) { + helper.noCopyOverride(paths, no_copy_files); + } + + walkDir(paths.core, handleDir.bind(null, true, in_path || paths.core), (filename, stats) => !no_copy_files.has(filename)); + walkDir(paths.vendor, handleDir.bind(null, true, in_path || paths.main), (filename, stats) => !no_copy_files.has(filename)); if (with_app_dir) { - walkDir(app_path, handleDir.bind(null, false, in_path || app_path), (filename, stats) => !no_copy_files.has(filename)); + walkDir(paths.app, handleDir.bind(null, false, in_path || paths.app), (filename, stats) => !no_copy_files.has(filename)); const out_app_path = path.join(out_path_base, 'app.js'); if (helper && helper.appWriter) { diff --git a/utils/use_require_helpers.js b/utils/use_require_helpers.js index b8ac46e6..990fb4df 100644 --- a/utils/use_require_helpers.js +++ b/utils/use_require_helpers.js @@ -11,6 +11,7 @@ module.exports = { console.log(`Please place RequireJS in ${path.join(base_out_path, 'require.js')}`); return ``; }, + noCopyOverride: () => {}, }, 'commonjs': { optionsOverride: (opts) => { @@ -23,12 +24,17 @@ module.exports = { b.bundle().pipe(fs.createWriteStream(out_path)); return ``; }, + noCopyOverride: () => {}, }, 'systemjs': { appWriter: (base_out_path, out_path) => { fs.writeFile(out_path, 'SystemJS.import("./app/ui.js");', (err) => { if (err) throw err; }); console.log(`Please place SystemJS in ${path.join(base_out_path, 'system-production.js')}`); - return `\n`; + return ` +\n`; + }, + noCopyOverride: (paths, no_copy_files) => { + no_copy_files.delete(path.join(paths.vendor, 'promise.js')); }, }, 'umd': { diff --git a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js index 0d22bab2..8d3a2eea 100644 --- a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js @@ -1343,9 +1343,9 @@ WorkerPool.prototype = { }, _stop: function () { - for (let wrkr of this._workers) { + this._workers.forEach(function(wrkr) { wrkr.terminate(); - } + }); } }; diff --git a/vendor/browser-es-module-loader/src/browser-es-module-loader.js b/vendor/browser-es-module-loader/src/browser-es-module-loader.js index a9d72097..6154e446 100644 --- a/vendor/browser-es-module-loader/src/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/src/browser-es-module-loader.js @@ -183,9 +183,9 @@ WorkerPool.prototype = { }, _stop: function () { - for (let wrkr of this._workers) { + this._workers.forEach(function(wrkr) { wrkr.terminate(); - } + }); } }; diff --git a/vendor/promise.js b/vendor/promise.js new file mode 100644 index 00000000..62843432 --- /dev/null +++ b/vendor/promise.js @@ -0,0 +1,255 @@ +/* Copyright (c) 2014 Taylor Hakes + * Copyright (c) 2014 Forbes Lindesay + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +(function (root) { + + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; + + function noop() {} + + // Polyfill for Function.prototype.bind + function bind(fn, thisArg) { + return function () { + fn.apply(thisArg, arguments); + }; + } + + function Promise(fn) { + if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); + if (typeof fn !== 'function') throw new TypeError('not a function'); + this._state = 0; + this._handled = false; + this._value = undefined; + this._deferreds = []; + + doResolve(fn, this); + } + + function handle(self, deferred) { + while (self._state === 3) { + self = self._value; + } + if (self._state === 0) { + self._deferreds.push(deferred); + return; + } + self._handled = true; + Promise._immediateFn(function () { + var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; + if (cb === null) { + (self._state === 1 ? resolve : reject)(deferred.promise, self._value); + return; + } + var ret; + try { + ret = cb(self._value); + } catch (e) { + reject(deferred.promise, e); + return; + } + resolve(deferred.promise, ret); + }); + } + + function resolve(self, newValue) { + try { + // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure + if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.'); + if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { + var then = newValue.then; + if (newValue instanceof Promise) { + self._state = 3; + self._value = newValue; + finale(self); + return; + } else if (typeof then === 'function') { + doResolve(bind(then, newValue), self); + return; + } + } + self._state = 1; + self._value = newValue; + finale(self); + } catch (e) { + reject(self, e); + } + } + + function reject(self, newValue) { + self._state = 2; + self._value = newValue; + finale(self); + } + + function finale(self) { + if (self._state === 2 && self._deferreds.length === 0) { + Promise._immediateFn(function() { + if (!self._handled) { + Promise._unhandledRejectionFn(self._value); + } + }); + } + + for (var i = 0, len = self._deferreds.length; i < len; i++) { + handle(self, self._deferreds[i]); + } + self._deferreds = null; + } + + function Handler(onFulfilled, onRejected, promise) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.promise = promise; + } + + /** + * Take a potentially misbehaving resolver function and make sure + * onFulfilled and onRejected are only called once. + * + * Makes no guarantees about asynchrony. + */ + function doResolve(fn, self) { + var done = false; + try { + fn(function (value) { + if (done) return; + done = true; + resolve(self, value); + }, function (reason) { + if (done) return; + done = true; + reject(self, reason); + }); + } catch (ex) { + if (done) return; + done = true; + reject(self, ex); + } + } + + Promise.prototype['catch'] = function (onRejected) { + return this.then(null, onRejected); + }; + + Promise.prototype.then = function (onFulfilled, onRejected) { + var prom = new (this.constructor)(noop); + + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; + + Promise.all = function (arr) { + var args = Array.prototype.slice.call(arr); + + return new Promise(function (resolve, reject) { + if (args.length === 0) return resolve([]); + var remaining = args.length; + + function res(i, val) { + try { + if (val && (typeof val === 'object' || typeof val === 'function')) { + var then = val.then; + if (typeof then === 'function') { + then.call(val, function (val) { + res(i, val); + }, reject); + return; + } + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); + } + } + + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; + + Promise.resolve = function (value) { + if (value && typeof value === 'object' && value.constructor === Promise) { + return value; + } + + return new Promise(function (resolve) { + resolve(value); + }); + }; + + Promise.reject = function (value) { + return new Promise(function (resolve, reject) { + reject(value); + }); + }; + + Promise.race = function (values) { + return new Promise(function (resolve, reject) { + for (var i = 0, len = values.length; i < len; i++) { + values[i].then(resolve, reject); + } + }); + }; + + // Use polyfill for setImmediate for performance gains + Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) || + function (fn) { + setTimeoutFunc(fn, 0); + }; + + Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) { + if (typeof console !== 'undefined' && console) { + console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console + } + }; + + /** + * Set the immediate function to execute callbacks + * @param fn {function} Function to execute + * @deprecated + */ + Promise._setImmediateFn = function _setImmediateFn(fn) { + Promise._immediateFn = fn; + }; + + /** + * Change the function to execute on unhandled rejection + * @param {function} fn Function to execute on unhandled rejection + * @deprecated + */ + Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) { + Promise._unhandledRejectionFn = fn; + }; + + if (typeof module !== 'undefined' && module.exports) { + module.exports = Promise; + } else if (!root.Promise) { + root.Promise = Promise; + } + +})(this); diff --git a/vnc.html b/vnc.html index 10cc1430..02fe5c69 100644 --- a/vnc.html +++ b/vnc.html @@ -63,6 +63,8 @@ + + From 7569cdc21edafe6c43308242055cedd674e6661d Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 1 Mar 2017 16:26:15 -0500 Subject: [PATCH 18/19] Fix vnc_auto.html This commit fixes `vnc_auto.html` to work with the new changes. Note that it is not translated over when `--with-app` is used on `util/use_require.js`. We'll probably want to deprecate it, or do some longer-term cleanup. --- vnc_auto.html | 190 ++++++++++++++++++++++++++------------------------ 1 file changed, 99 insertions(+), 91 deletions(-) diff --git a/vnc_auto.html b/vnc_auto.html index e4fc4676..07eeeeba 100644 --- a/vnc_auto.html +++ b/vnc_auto.html @@ -22,18 +22,33 @@ Remove this if you use the .htaccess --> + + + + + + + + + + + + + + + + + + - - - - - - + + + + + @@ -42,52 +57,19 @@ --> - - - - - -
-
- - - -
- Loading -
- - - - - - -
-
- - Canvas not supported. - -
- - + + + })(); + + + + +
+
+ + + +
+ Loading +
+ + + + + + +
+
+ + Canvas not supported. + +
From d6c17390f0e4199d094e4af3a8a5e97c98f2c579 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 1 Mar 2017 21:10:09 -0500 Subject: [PATCH 19/19] Make vnc_playback.html functional once more This commit makes vnc_playback.html functional once more, and completely refactors tests/playback.js to make it usable in other scenarios. In order for vnc_playback.js to properly load playback files now, they must `export` their variables. --- tests/playback-ui.js | 174 +++++++++++ tests/playback.js | 279 +++++++++--------- tests/vnc_playback.html | 100 +------ .../dist/browser-es-module-loader.js | 8 +- .../src/browser-es-module-loader.js | 8 +- 5 files changed, 330 insertions(+), 239 deletions(-) create mode 100644 tests/playback-ui.js diff --git a/tests/playback-ui.js b/tests/playback-ui.js new file mode 100644 index 00000000..f47b9d86 --- /dev/null +++ b/tests/playback-ui.js @@ -0,0 +1,174 @@ +import * as WebUtil from '../app/webutil.js'; +import RecordingPlayer from './playback.js'; + +var frames = null; +var encoding = null; + +function message(str) { + console.log(str); + var cell = document.getElementById('messages'); + cell.textContent += str + "\n"; + cell.scrollTop = cell.scrollHeight; +} + +function loadFile() { + const fname = WebUtil.getQueryVar('data', null); + + if (!fname) { + return Promise.reject("Must specify data=FOO in query string."); + } + + message("Loading " + fname); + return import(`../recordings/${fname}#nocache`); +} + +function enableUI(recording) { + var iterations = WebUtil.getQueryVar('iterations', 3); + document.getElementById('iterations').value = iterations; + + var mode = WebUtil.getQueryVar('mode', 3); + if (mode === 'realtime') { + document.getElementById('mode2').checked = true; + } else { + document.getElementById('mode1').checked = true; + } + + message("VNC_frame_data.length: " + recording.VNC_frame_data.length); + + const startButton = document.getElementById('startButton'); + startButton.disabled = false + startButton.addEventListener('click', start); + + frames = recording.VNC_frame_data; + encoding = recording.VNC_frame_encoding; +} + +const notification = function (rfb, mesg, level, options) { + document.getElementById('VNC_status').textContent = mesg; +} + +function IterationPlayer (iterations, frames, encoding) { + this._iterations = iterations; + + this._iteration = undefined; + this._player = undefined; + + this._start_time = undefined; + + this._frames = frames; + this._encoding = encoding; + + this._state = 'running'; + + this.onfinish = function() {}; + this.oniterationfinish = function() {}; + this.rfbdisconnected = function() {}; + this.rfbnotification = function() {}; +} + +IterationPlayer.prototype = { + start: function (mode) { + this._iteration = 0; + this._start_time = (new Date()).getTime(); + + this._realtime = mode.startsWith('realtime'); + this._trafficMgmt = !mode.endsWith('-no-mgmt'); + + this._nextIteration(); + }, + + _nextIteration: function () { + const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this), this._notification.bind(this)); + player.onfinish = this._iterationFinish.bind(this); + + if (this._state !== 'running') { return; } + + this._iteration++; + if (this._iteration > this._iterations) { + this._finish(); + return; + } + + player.run(this._realtime, this._trafficMgmt); + }, + + _finish: function () { + const endTime = (new Date()).getTime(); + const totalDuration = endTime - this._start_time; + + const evt = new Event('finish'); + evt.duration = totalDuration; + evt.iterations = this._iterations; + this.onfinish(evt); + }, + + _iterationFinish: function (duration) { + const evt = new Event('iterationfinish'); + evt.duration = duration; + evt.number = this._iteration; + this.oniterationfinish(evt); + + this._nextIteration(); + }, + + _disconnected: function (rfb, reason, frame) { + if (reason) { + this._state = 'failed'; + } + + var evt = new Event('rfbdisconnected'); + evt.reason = reason; + evt.frame = frame; + + this.onrfbdisconnected(evt); + }, + + _notification: function (rfb, msg, level, options) { + var evt = new Event('rfbnotification'); + evt.message = msg; + evt.level = level; + evt.options = options; + + this.onrfbnotification(evt); + }, +}; + +function start() { + document.getElementById('startButton').value = "Running"; + document.getElementById('startButton').disabled = true; + + const iterations = document.getElementById('iterations').value; + + var mode; + + if (document.getElementById('mode1').checked) { + message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`); + mode = 'perftest'; + } else { + message(`Starting realtime playback [${iterations} iteration(s)]`); + mode = 'realtime'; + } + + const player = new IterationPlayer(iterations, frames, encoding); + player.oniterationfinish = function (evt) { + message(`Iteration ${evt.number} took ${evt.duration}ms`); + }; + player.onrfbdisconnected = function (evt) { + if (evt.reason) { + message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`); + } + }; + player.onrfbnotification = function (evt) { + document.getElementById('VNC_status').textContent = evt.message; + }; + player.onfinish = function (evt) { + const iterTime = parseInt(evt.duration / evt.iterations, 10); + message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`); + + document.getElementById('startButton').disabled = false; + document.getElementById('startButton').value = "Start"; + }; + player.start(mode); +} + +loadFile().then(enableUI).catch(message); diff --git a/tests/playback.js b/tests/playback.js index a25fac49..b39c9818 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -4,29 +4,17 @@ * Licensed under MPL 2.0 (see LICENSE.txt) */ -"use strict"; -/*jslint browser: true, white: false */ -/*global Util, VNC_frame_data, finish */ - -var rfb, mode, test_state, frame_idx, frame_length, - iteration, iterations, istart_time, encoding, - - // Pre-declarations for jslint - send_array, next_iteration, end_iteration, queue_next_packet, - do_packet, enable_test_mode; - -// Override send_array -send_array = function (arr) { - // Stub out send_array -}; +import RFB from '../core/rfb.js'; +import * as Log from '../core/util/logging.js'; +import Base64 from '../core/base64.js'; // Immediate polyfill -if (window.setImmediate === undefined) { +if (setImmediate === undefined) { var _immediateIdCounter = 1; var _immediateFuncs = {}; - window.setImmediate = function (func) { - var index = Util._immediateIdCounter++; + var setImmediate = function (func) { + var index = _immediateIdCounter++; _immediateFuncs[index] = func; window.postMessage("noVNC immediate trigger:" + index, "*"); return index; @@ -56,143 +44,160 @@ if (window.setImmediate === undefined) { window.addEventListener("message", _onMessage); } -enable_test_mode = function () { - rfb._sock.send = send_array; - rfb._sock.close = function () {}; - rfb._sock.flush = function () {}; - rfb._checkEvents = function () {}; - rfb.connect = function (host, port, password, path) { - this._rfb_host = host; - this._rfb_port = port; - this._rfb_password = (password !== undefined) ? password : ""; - this._rfb_path = (path !== undefined) ? path : ""; - this._sock.init('binary', 'ws'); - this._rfb_connection_state = 'connecting'; - this._rfb_init_state = 'ProtocolVersion'; - }; -}; +export default function RecordingPlayer (frames, encoding, disconnected, notification) { + this._frames = frames; + this._encoding = encoding; -next_iteration = function () { - rfb = new RFB({'target': document.getElementById('VNC_canvas'), - 'view_only': true, - 'onDisconnected': disconnected, - 'onNotification': notification}); - enable_test_mode(); + this._disconnected = disconnected; + this._notification = notification; - // Missing in older recordings - if (typeof VNC_frame_encoding === 'undefined') { - var frame = VNC_frame_data[0]; - var start = frame.indexOf('{', 1) + 1; + if (this._encoding === undefined) { + let frame = this._frames[0]; + let start = frame.indexOf('{', 1) + 1; if (frame.slice(start).startsWith('UkZC')) { - encoding = 'base64'; + this._encoding = 'base64'; } else { - encoding = 'binary'; + this._encoding = 'binary'; } - } else { - encoding = VNC_frame_encoding; } - if (iteration === 0) { - frame_length = VNC_frame_data.length; - test_state = 'running'; - } + this._rfb = undefined; + this._frame_length = this._frames.length; - if (test_state !== 'running') { return; } + this._frame_index = 0; + this._start_time = undefined; + this._realtime = true; + this._trafficManagement = true; - iteration += 1; - if (iteration > iterations) { - finish(); - return; - } + this._running = false; - frame_idx = 0; - istart_time = (new Date()).getTime(); - rfb.connect('test', 0, "bogus"); + this.onfinish = function () {}; +} - queue_next_packet(); +RecordingPlayer.prototype = { + run: function (realtime, trafficManagement) { + // initialize a new RFB + this._rfb = new RFB({'target': document.getElementById('VNC_canvas'), + 'view_only': true, + 'onDisconnected': this._handleDisconnect.bind(this), + 'onNotification': this._notification}); + this._enablePlaybackMode(); -}; + // reset the frame index and timer + this._frame_index = 0; + this._start_time = (new Date()).getTime(); -end_iteration = function () { - if (rfb._display.pending()) { - rfb._display.set_onFlush(function () { - if (rfb._flushing) { - rfb._onFlush(); + this._realtime = realtime; + this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement; + + this._running = true; + + // launch the tests + this._rfb.connect('test', 0, 'bogus'); + + this._queueNextPacket(); + }, + + // _enablePlaybackMode mocks out things not required for running playback + _enablePlaybackMode: function () { + this._rfb._sock.send = function (arr) {}; + this._rfb._sock.close = function () {}; + this._rfb._sock.flush = function () {}; + this._rfb._checkEvents = function () {}; + this._rfb.connect = function (host, port, password, path) { + this._rfb_host = host; + this._rfb_port = port; + this._rfb_password = (password !== undefined) ? password : ""; + this._rfb_path = (path !== undefined) ? path : ""; + this._sock.init('binary', 'ws'); + this._rfb_connection_state = 'connecting'; + this._rfb_init_state = 'ProtocolVersion'; + }; + }, + + _queueNextPacket: function () { + if (!this._running) { return; } + + var frame = this._frames[this._frame_index]; + + // skip send frames + while (this._frame_index < this._frame_length && frame.charAt(0) === "}") { + this._frame_index++; + frame = this._frames[this._frame_index]; + } + + if (frame === 'EOF') { + Log.Debug('Finished, found EOF'); + this._finish(); + return; + } + + if (this._frame_index >= this._frame_length) { + Log.Debug('Finished, no more frames'); + this._finish(); + return; + } + + if (this._realtime) { + let foffset = frame.slice(1, frame.indexOf('{', 1)); + let toffset = (new Date()).getTime() - this._start_time; + let delay = foffset - toffset; + if (delay < 1) delay = 1; + + setTimeout(this._doPacket.bind(this), delay); + } else { + setImmediate(this._doPacket.bind(this)); + } + }, + + _doPacket: function () { + // Avoid having excessive queue buildup in non-realtime mode + if (!this._trafficManagement && this._rfb._flushing) { + let player = this; + this._rfb.display.set_onFlush(function () { + this._rfb._display.set_onFlush(this._rfb._onFlush.bind(this._rfb)); + this._rfb._onFlush(); + player._doPacket(); + }); + return; + } + + const frame = this._frames[this._frame_index]; + var start = frame.indexOf('{', 1) + 1; + if (this._encoding === 'base64') { + var u8 = Base64.decode(frame.slice(start)); + start = 0; + } else { + var u8 = new Uint8Array(frame.length - start); + for (let i = 0; i < frame.length - start; i++) { + u8[i] = frame.charCodeAt(start + i); } - end_iteration(); - }); - rfb._display.flush(); - } else { - next_iteration(); - } -}; - -queue_next_packet = function () { - var frame, foffset, toffset, delay; - if (test_state !== 'running') { return; } - - frame = VNC_frame_data[frame_idx]; - while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) { - //Util.Debug("Send frame " + frame_idx); - frame_idx += 1; - frame = VNC_frame_data[frame_idx]; - } - - if (frame === 'EOF') { - Util.Debug("Finished, found EOF"); - end_iteration(); - return; - } - if (frame_idx >= frame_length) { - Util.Debug("Finished, no more frames"); - end_iteration(); - return; - } - - if (mode === 'realtime') { - foffset = frame.slice(1, frame.indexOf('{', 1)); - toffset = (new Date()).getTime() - istart_time; - delay = foffset - toffset; - if (delay < 1) { - delay = 1; } - setTimeout(do_packet, delay); - } else { - window.setImmediate(do_packet); - } -}; + this._rfb._sock._recv_message({'data': u8}); + this._frame_index++; -var bytes_processed = 0; + this._queueNextPacket(); + }, -do_packet = function () { - // Avoid having an excessive queue buildup - if (rfb._flushing && (mode !== 'realtime')) { - rfb._display.set_onFlush(function () { - rfb._display.set_onFlush(rfb._onFlush.bind(rfb)); - rfb._onFlush(); - do_packet(); - }); - return; - } - - //Util.Debug("Processing frame: " + frame_idx); - var frame = VNC_frame_data[frame_idx], - start = frame.indexOf('{', 1) + 1; - var u8; - if (encoding === 'base64') { - u8 = Base64.decode(frame.slice(start)); - start = 0; - } else { - u8 = new Uint8Array(frame.length - start); - for (var i = 0; i < frame.length - start; i++) { - u8[i] = frame.charCodeAt(start + i); + _finish() { + if (this._rfb._display.pending()) { + var player = this; + this._rfb._display.set_onFlush(function () { + if (player._rfb._flushing) { + player._rfb._onFlush(); + } + player._finish(); + }); + this._rfb._display.flush(); + } else { + this._running = false; + this.onfinish((new Date()).getTime() - this._start_time); } + }, + + _handleDisconnect(rfb, reason) { + this._running = false; + this._disconnected(rfb, reason, this._frame_index); } - bytes_processed += u8.length; - rfb._sock._recv_message({'data' : u8}); - frame_idx += 1; - - queue_next_packet(); }; - diff --git a/tests/vnc_playback.html b/tests/vnc_playback.html index 65b735e7..66f40576 100644 --- a/tests/vnc_playback.html +++ b/tests/vnc_playback.html @@ -2,6 +2,8 @@ VNC Playback + + @@ -9,8 +11,7 @@ Perftest:  Realtime:   -   +  

@@ -37,98 +38,5 @@ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'> --> - - - - - +