From efed2eeafd7f80f012fffbb18c05079db2bdbbd7 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Fri, 15 May 2015 14:27:23 -0400 Subject: [PATCH 01/10] Fix up vnc_playback.html and playback.js This commit fixes vnc_playback.html and playback.js so that they work with the current version of noVNC. --- include/playback.js | 27 ++++++++++++++++++++++----- tests/vnc_playback.html | 9 ++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/include/playback.js b/include/playback.js index 7756529d..7d3d749f 100644 --- a/include/playback.js +++ b/include/playback.js @@ -12,19 +12,36 @@ var rfb, mode, test_state, frame_idx, frame_length, iteration, iterations, istart_time, // Pre-declarations for jslint - send_array, next_iteration, queue_next_packet, do_packet; + send_array, next_iteration, queue_next_packet, do_packet, enable_test_mode; // Override send_array send_array = function (arr) { // Stub out send_array }; +enable_test_mode = function () { + rfb._sock._mode = VNC_frame_encoding; + 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._updateState('ProtocolVersion', "Starting VNC handshake"); + }; +}; + next_iteration = function () { + rfb = new RFB({'target': $D('VNC_canvas'), + 'onUpdateState': updateState}); + enable_test_mode(); + if (iteration === 0) { frame_length = VNC_frame_data.length; test_state = 'running'; - } else { - rfb.disconnect(); } if (test_state !== 'running') { return; } @@ -91,9 +108,9 @@ do_packet = function () { for (var i = 0; i < frame.length - start; i++) { u8[i] = frame.charCodeAt(start + i); } - rfb.recv_message({'data' : u8}); + rfb._sock._recv_message({'data' : u8}); } else { - rfb.recv_message({'data' : frame.slice(start)}); + rfb._sock._recv_message({'data' : frame.slice(start)}); } frame_idx += 1; diff --git a/tests/vnc_playback.html b/tests/vnc_playback.html index b5faf93c..8b0207b2 100644 --- a/tests/vnc_playback.html +++ b/tests/vnc_playback.html @@ -59,7 +59,7 @@ if (fname) { message("Loading " + fname); // Load supporting scripts - Util.load_scripts(["base64.js", "websock.js", "des.js", + Util.load_scripts(["base64.js", "websock.js", "des.js", "keysym.js", "keysymdef.js", "keyboard.js", "input.js", "display.js", "jsunzip.js", "rfb.js", "playback.js", fname]); @@ -75,7 +75,6 @@ test_state = 'failed'; break; case 'loaded': - $D('startButton').disabled = false; break; } if (typeof msg !== 'undefined') { @@ -99,7 +98,8 @@ mode = 'realtime'; } - recv_message = rfb.testMode(send_array, VNC_frame_encoding); + //recv_message = rfb.testMode(send_array, VNC_frame_encoding); + next_iteration(); } @@ -130,9 +130,8 @@ } if (fname) { message("VNC_frame_data.length: " + VNC_frame_data.length); - rfb = new RFB({'target': $D('VNC_canvas'), - 'onUpdateState': updateState}); } + $D('startButton').disabled = false; } From 0442e153a1cad88e823926e596bcb3cff0aacabe Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 4 Aug 2015 15:50:20 -0400 Subject: [PATCH 02/10] Fix RFB.js JSHint Errors This fixes a couple of JSHint errors in RFB.js caused by using `==` instead of `===`. --- include/rfb.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index a591ca2b..4e8f4087 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -1903,9 +1903,9 @@ var RFB; this._sock.rQskipBytes(1); // number-of-screens this._sock.rQskipBytes(3); // padding - for (var i=0; i Date: Mon, 18 May 2015 19:01:58 -0400 Subject: [PATCH 03/10] WIP: Switch to Pako for zlib This commit introduces an alternate implementation of the zlib decompressor based on Pako (https://github.com/nodeca/pako). --- LICENSE.txt | 4 + README.md | 3 +- docs/LICENSE.pako | 21 + docs/notes | 6 + include/inflator.js | 2409 +++++++++++++++++++++++++++++++++++++ include/rfb.js | 24 +- include/ui.js | 2 +- karma.conf.js | 2 +- tests/test.rfb.js | 2 +- tests/vnc_playback.html | 2 +- utils/inflator.partial.js | 32 + vnc_auto.html | 2 +- 12 files changed, 2495 insertions(+), 14 deletions(-) create mode 100644 docs/LICENSE.pako create mode 100644 include/inflator.js create mode 100644 utils/inflator.partial.js diff --git a/LICENSE.txt b/LICENSE.txt index e896efca..638cd5a5 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -59,6 +59,9 @@ licenses (all MPL 2.0 compatible): utils/websockify utils/websocket.py : LGPL 3 + + utils/inflator.partial.js + include/inflator.js : MIT (for pako) The following license texts are included: @@ -70,6 +73,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 Or alternatively the license texts may be found here: diff --git a/README.md b/README.md index b5679cdd..0f9a8f6f 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,5 @@ use a WebSockets to TCP socket proxy. There is a python proxy included * web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js) * as3crypto : Henri Torgemane (code.google.com/p/as3crypto) * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net) - * jsunzip : Erik Moller (github.com/operasoftware/jsunzip), - * tinflate : Joergen Ibsen (ibsensoftware.com) * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs) + * Pako : Vitaly Puzrin (https://github.com/nodeca/pako) diff --git a/docs/LICENSE.pako b/docs/LICENSE.pako new file mode 100644 index 00000000..e6c9e5a5 --- /dev/null +++ b/docs/LICENSE.pako @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (C) 2014 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/docs/notes b/docs/notes index 9bcc6af3..443dbd41 100644 --- a/docs/notes +++ b/docs/notes @@ -15,3 +15,9 @@ Building web-socket-js emulator: cd include/web-socket-js/flash-src mxmlc -static-link-runtime-shared-libraries WebSocketMain.as + +Rebuilding inflator.js + +- Download pako from npm +- Install browserify using npm +- browserify utils/inflator.partial.js -o include/inflator.js diff --git a/include/inflator.js b/include/inflator.js new file mode 100644 index 00000000..68f85cbd --- /dev/null +++ b/include/inflator.js @@ -0,0 +1,2409 @@ +(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; + +},{}],3:[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 = crc ^ (-1); + + for (var i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + + +module.exports = crc32; + +},{}],4:[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 */ + var 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; + 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 = window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = 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++] = window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = 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++] = 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; +}; + +},{}],5:[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 /*crc32(0L, Z_NULL, 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; +} + + +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.inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +exports.inflateCopy = inflateCopy; +exports.inflateGetDictionary = inflateGetDictionary; +exports.inflateMark = inflateMark; +exports.inflatePrime = inflatePrime; +exports.inflateSetDictionary = inflateSetDictionary; +exports.inflateSync = inflateSync; +exports.inflateSyncPoint = inflateSyncPoint; +exports.inflateUndermine = inflateUndermine; +*/ + +},{"../utils/common":1,"./adler32":2,"./crc32":3,"./inffast":4,"./inftrees":6}],6:[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":1}],7:[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; + +},{}],"/partial_inflator.js":[function(require,module,exports){ +var zlib = require('./lib/zlib/inflate.js'); +var ZStream = require('./lib/zlib/zstream.js'); + +var Inflate = function () { + 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) { + this.strm.input = new Uint8Array(data); + this.strm.avail_in = this.strm.input.length; + this.strm.next_in = 0; + this.strm.next_out = 0; + + 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}; + +},{"./lib/zlib/inflate.js":5,"./lib/zlib/zstream.js":7}]},{},[])("/partial_inflator.js") +}); diff --git a/include/rfb.js b/include/rfb.js index 4e8f4087..3b54701e 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -380,8 +380,9 @@ var RFB; } for (i = 0; i < 4; i++) { - this._FBU.zlibs[i] = new TINF(); - this._FBU.zlibs[i].init(); + //this._FBU.zlibs[i] = new TINF(); + //this._FBU.zlibs[i].init(); + this._FBU.zlibs[i] = new inflator.Inflate(); } }, @@ -1181,7 +1182,14 @@ var RFB; this._timing.last_fbu = (new Date()).getTime(); - ret = this._encHandlers[this._FBU.encoding](); + var handler = this._encHandlers[this._FBU.encoding]; + try { + //ret = this._encHandlers[this._FBU.encoding](); + ret = handler(); + } catch (ex) { + console.log("missed " + this._FBU.encoding + ": " + handler); + ret = this._encHandlers[this._FBU.encoding](); + } now = (new Date()).getTime(); this._timing.cur_fbu += (now - this._timing.last_fbu); @@ -1639,12 +1647,14 @@ var RFB; } } - var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); - if (uncompressed.status !== 0) { + //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); + var uncompressed = this._FBU.zlibs[streamId].inflate(data, true); + /*if (uncompressed.status !== 0) { Util.Error("Invalid data in zlib stream"); - } + }*/ - return uncompressed.data; + //return uncompressed.data; + return uncompressed; }.bind(this); var indexedToRGB = function (data, numColors, palette, width, height) { diff --git a/include/ui.js b/include/ui.js index 2e9d731a..929e37f8 100644 --- a/include/ui.js +++ b/include/ui.js @@ -21,7 +21,7 @@ var UI; window.onscriptsload = function () { UI.load(); }; Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js", "keysymdef.js", "keyboard.js", "input.js", "display.js", - "jsunzip.js", "rfb.js", "keysym.js"]); + "rfb.js", "keysym.js", "inflator.js"]); UI = { diff --git a/karma.conf.js b/karma.conf.js index d8b8e905..870b8551 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -119,9 +119,9 @@ module.exports = function(config) { 'include/input.js', 'include/websock.js', 'include/rfb.js', - 'include/jsunzip.js', 'include/des.js', 'include/display.js', + 'include/inflator.js', 'tests/test.*.js' ], diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 006b5fa9..5a6ac4a8 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1,4 +1,4 @@ -// requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, jsunzip, des, display +// requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, inflator, des, display // requires test modules: fake.websocket, assertions /* jshint expr: true */ var assert = chai.assert; diff --git a/tests/vnc_playback.html b/tests/vnc_playback.html index 8b0207b2..f36f1e65 100644 --- a/tests/vnc_playback.html +++ b/tests/vnc_playback.html @@ -61,7 +61,7 @@ // Load supporting scripts Util.load_scripts(["base64.js", "websock.js", "des.js", "keysym.js", "keysymdef.js", "keyboard.js", "input.js", "display.js", - "jsunzip.js", "rfb.js", "playback.js", fname]); + "rfb.js", "playback.js", "inflator.js", fname]); } else { message("Must specify data=FOO in query string."); diff --git a/utils/inflator.partial.js b/utils/inflator.partial.js new file mode 100644 index 00000000..2522d781 --- /dev/null +++ b/utils/inflator.partial.js @@ -0,0 +1,32 @@ +var zlib = require('./lib/zlib/inflate.js'); +var ZStream = require('./lib/zlib/zstream.js'); + +var Inflate = function () { + 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) { + this.strm.input = data; + this.strm.avail_in = this.strm.input.length; + this.strm.next_in = 0; + this.strm.next_out = 0; + + 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/vnc_auto.html b/vnc_auto.html index 4361a5d5..86cfde75 100644 --- a/vnc_auto.html +++ b/vnc_auto.html @@ -77,7 +77,7 @@ // Load supporting scripts Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js", "keysymdef.js", "keyboard.js", "input.js", "display.js", - "jsunzip.js", "rfb.js", "keysym.js"]); + "inflator.js", "rfb.js", "keysym.js"]); var rfb; var resizeTimeout; From 6c883653a23aec7220b7b8cd042c90554fcb28cc Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 19 May 2015 16:49:39 -0400 Subject: [PATCH 04/10] Remove unecessarily nested loop This commit removes a nested loop in indexedToRGB, converting it into a plain loop without multiplication. --- include/rfb.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index 3b54701e..47409c67 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -91,6 +91,7 @@ var RFB; this._fb_width = 0; this._fb_height = 0; this._fb_name = ""; + this._dest_buff = null; this._rre_chunk_sz = 100; @@ -901,6 +902,7 @@ var RFB; /* Screen size */ this._fb_width = this._sock.rQshift16(); this._fb_height = this._sock.rQshift16(); + this._dest_buff = new Uint8Array(this._fb_width * this._fb_height * 4); /* PIXEL_FORMAT */ var bpp = this._sock.rQshift8(); @@ -1660,7 +1662,7 @@ var RFB; var indexedToRGB = function (data, numColors, palette, width, height) { // Convert indexed (palette based) image data to RGB // TODO: reduce number of calculations inside loop - var dest = []; + var dest = this._dest_buff; var x, y, dp, sp; if (numColors === 2) { var w = Math.floor((width + 7) / 8); @@ -1687,14 +1689,12 @@ var RFB; } } } else { - for (y = 0; y < height; y++) { - for (x = 0; x < width; x++) { - dp = (y * width + x) * 3; - sp = data[y * width + x] * 3; - dest[dp] = palette[sp]; - dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp + 2]; - } + var total = width * height * 3; + for (var i = 0, j = 0; i < total; i += 3, j++) { + sp = data[j] * 3; + dest[i] = palette[sp]; + dest[i + 1] = palette[sp + 1]; + dest[i + 2] = palette[sp + 2]; } } @@ -1891,6 +1891,7 @@ var RFB; handle_FB_resize: function () { this._fb_width = this._FBU.width; this._fb_height = this._FBU.height; + this._dest_buff = 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(); From 38781d931ec18304f51ed3469faff8387e3cbc55 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 20 May 2015 17:10:59 -0400 Subject: [PATCH 05/10] Use Typed Arrays for the Websock receive queue **This commit removes Base64 (and Flash) support** This commit converts websock.js to used Typed Arrays for the receive queue (and tweaks rfb.js to ensure that it continues to function, since only Firefox implements `%TypedArray%.prototype.slice`). Base64 support was removed to simplify code paths, and pave the way for using Typed Arrays for the send queue as well. This provides two advantages: first, we allocate a buffer ahead of time, meaning the browser doesn't have to do any work dynamically increasing the receive queue size. Secondly, we are now able to pass around Typed Array Views (e.g. `Uint8Array`), which are lightweight, and don't involve copying. The downside is that we initially allocate more memory -- we currently start out with 4 MiB, and then automatically double when it looks like the amount unused is getting to small. The commit also explicitly adds a check to the compacting logic that avoids calling the copy functions if `_rQlen === _rQi`. --- .gitmodules | 3 - LICENSE.txt | 3 - README.md | 7 +- docs/notes | 18 -- include/playback.js | 3 +- include/rfb.js | 40 +-- include/web-socket-js-project | 1 - include/web-socket-js/README.txt | 109 ------- include/web-socket-js/WebSocketMain.swf | Bin 177139 -> 0 bytes include/web-socket-js/swfobject.js | 4 - include/web-socket-js/web_socket.js | 391 ------------------------ include/websock.js | 170 ++++++----- tests/assertions.js | 67 +++- tests/test.rfb.js | 27 +- tests/test.websock.js | 114 +++---- 15 files changed, 242 insertions(+), 715 deletions(-) delete mode 160000 include/web-socket-js-project delete mode 100644 include/web-socket-js/README.txt delete mode 100644 include/web-socket-js/WebSocketMain.swf delete mode 100644 include/web-socket-js/swfobject.js delete mode 100644 include/web-socket-js/web_socket.js diff --git a/.gitmodules b/.gitmodules index 45574aeb..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "include/web-socket-js-project"] - path = include/web-socket-js-project - url = https://github.com/gimite/web-socket-js.git diff --git a/LICENSE.txt b/LICENSE.txt index 638cd5a5..82e8a6a1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -51,9 +51,6 @@ licenses (all MPL 2.0 compatible): include/jsunzip.js : zlib/libpng license - include/web-socket-js/ : New BSD license (3-clause). Source code at - http://github.com/gimite/web-socket-js - include/chrome-app/tcp-stream.js : Apache 2.0 license diff --git a/README.md b/README.md index 0f9a8f6f..59a72b10 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,7 @@ See more screenshots h * HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS Safari, Opera 11+, Internet Explorer 9+, etc. -* HTML5 WebSockets: For browsers that do not have builtin - WebSockets support, the project includes - web-socket-js, - a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in - WebSocket support. +* HTML5 WebSockets and Typed Arrays * Fast Javascript Engine: this is not strictly a requirement, but without a fast Javascript engine, noVNC might be painfully slow. @@ -130,7 +126,6 @@ use a WebSockets to TCP socket proxy. There is a python proxy included * tight encoding : Michael Tinglof (Mercuri.ca) * Included libraries: - * web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js) * as3crypto : Henri Torgemane (code.google.com/p/as3crypto) * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net) * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs) diff --git a/docs/notes b/docs/notes index 443dbd41..6ff5ec19 100644 --- a/docs/notes +++ b/docs/notes @@ -1,21 +1,3 @@ -Some implementation notes: - -There is an included flash object (web-socket-js) that is used to -emulate websocket support on browsers without websocket support -(currently only Chrome has WebSocket support). - -Javascript doesn't have a bytearray type, so what you get out of -a WebSocket object is just Javascript strings. Javascript has UTF-16 -unicode strings and anything sent through the WebSocket gets converted -to UTF-8 and vice-versa. So, one additional (and necessary) function -of websockify is base64 encoding/decoding what is sent to/from the -browser. - -Building web-socket-js emulator: - -cd include/web-socket-js/flash-src -mxmlc -static-link-runtime-shared-libraries WebSocketMain.as - Rebuilding inflator.js - Download pako from npm diff --git a/include/playback.js b/include/playback.js index 7d3d749f..203576f6 100644 --- a/include/playback.js +++ b/include/playback.js @@ -30,6 +30,7 @@ enable_test_mode = function () { this._rfb_port = port; this._rfb_password = (password !== undefined) ? password : ""; this._rfb_path = (path !== undefined) ? path : ""; + this._sock.init('binary', 'ws'); this._updateState('ProtocolVersion', "Starting VNC handshake"); }; }; @@ -43,7 +44,7 @@ next_iteration = function () { frame_length = VNC_frame_data.length; test_state = 'running'; } - + if (test_state !== 'running') { return; } iteration += 1; diff --git a/include/rfb.js b/include/rfb.js index 47409c67..113d9419 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -129,7 +129,7 @@ var RFB; 'view_only': false, // Disable client mouse/keyboard 'xvp_password_sep': '@', // Separator for XVP password fields 'disconnectTimeout': 3, // Time (s) to wait for disconnection - 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection + 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection 'repeaterID': '', // [UltraVNC] RepeaterID to connect to 'viewportDrag': false, // Move the viewport on mouse drags @@ -218,16 +218,8 @@ var RFB; Util.Info("Using native WebSockets"); this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); } else { - Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version); - if (!Util.Flash || Util.Flash.version < 9) { - this._cleanupSocket('fatal'); - throw new Exception("WebSockets or Adobe Flash is required"); - } else if (document.location.href.substr(0, 7) === 'file://') { - this._cleanupSocket('fatal'); - throw new Exception("'file://' URL is incompatible with Adobe Flash"); - } else { - this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode); - } + this._cleanupSocket('fatal'); + throw new Error("WebSocket support is required to use noVNC"); } Util.Debug("<< RFB.constructor"); @@ -363,8 +355,6 @@ var RFB; _init_vars: function () { // reset state - this._sock.init(); - this._FBU.rects = 0; this._FBU.subrects = 0; // RRE and HEXTILE this._FBU.lines = 0; // RAW @@ -760,7 +750,8 @@ var RFB; if (this._sock.rQwait("auth challenge", 16)) { return false; } - var challenge = this._sock.rQshiftBytes(16); + // 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._updateState("SecurityResult"); @@ -1559,11 +1550,21 @@ var RFB; rQi += this._FBU.bytes - 1; } else { if (this._FBU.subencoding & 0x02) { // Background - this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp); + 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; } if (this._FBU.subencoding & 0x04) { // Foreground - this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp); + 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; } @@ -1575,7 +1576,12 @@ var RFB; for (var s = 0; s < subrects; s++) { var color; if (this._FBU.subencoding & 0x10) { // SubrectsColoured - color = rQ.slice(rQi, rQi + this._fb_Bpp); + 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; } else { color = this._FBU.foreground; diff --git a/include/web-socket-js-project b/include/web-socket-js-project deleted file mode 160000 index c0855c6c..00000000 --- a/include/web-socket-js-project +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c0855c6caec589c33acc22b6ee5e562287e65f3d diff --git a/include/web-socket-js/README.txt b/include/web-socket-js/README.txt deleted file mode 100644 index 2e32ea7f..00000000 --- a/include/web-socket-js/README.txt +++ /dev/null @@ -1,109 +0,0 @@ -* How to try - -Assuming you have Web server (e.g. Apache) running at http://example.com/ . - -- Download web_socket.rb from: - http://github.com/gimite/web-socket-ruby/tree/master -- Run sample Web Socket server (echo server) in example.com with: (#1) - $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081 -- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details. -- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html). -- Change ws://localhost:10081 to ws://example.com:10081 in sample.html. -- Open sample.html in your browser. -- After "onopen" is shown, input something, click [Send] and confirm echo back. - -#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com. - - -* Troubleshooting - -If it doesn't work, try these: - -1. Try Chrome and Firefox 3.x. -- It doesn't work on Chrome: --- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log. -- It works on Chrome but it doesn't work on Firefox: --- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below). -- It works on both Chrome and Firefox, but it doesn't work on your browser: --- Check "Supported environment" section below. Your browser may not be supported by web-socket-js. - -2. Add this line before your code: - WEB_SOCKET_DEBUG = true; -and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors. - -3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html. - -4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details. - -5. Check if sample.html bundled with web-socket-js works. - -6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall. - -7. Install debugger version of Flash Player available here to see Flash errors: -http://www.adobe.com/support/flashplayer/downloads.html - - -* Supported environments - -It should work on: -- Google Chrome 4 or later (just uses native implementation) -- Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later - -It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself. - - -* Flash socket policy file - -This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash. - -If you use web-socket-ruby available at -http://github.com/gimite/web-socket-ruby/tree/master -, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides. - -If you use other Web Socket server implementation, you need to provide socket policy file yourself. See -http://www.lightsphere.com/dev/articles/flash_socket_policy.html -for details and sample script to run socket policy file server. node.js implementation is available here: -http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js - -Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster. - - -* Cookie considerations - -Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard). - -Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin. - - -* Proxy considerations - -The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method. - -The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy. - -The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails. - - -* How to host HTML file and SWF file in different domains - -By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain. - -WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie. - -- Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf. -- Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf. -- In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf. - - -* How to build WebSocketMain.swf - -Install Flex 4 SDK: -http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4 - -$ cd flash-src -$ ./build.sh - - -* License - -New BSD License. diff --git a/include/web-socket-js/WebSocketMain.swf b/include/web-socket-js/WebSocketMain.swf deleted file mode 100644 index f286c81aacc954953086a97dfff7e4e18e78b6bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177139 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�{?S0&?Rq)c&c-(}SFHOPy4tz`b5gun?zakh{ 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:-!a@ddza^dI1O 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 D+>;X~ diff --git a/include/web-socket-js/swfobject.js b/include/web-socket-js/swfobject.js deleted file mode 100644 index 8eafe9dd..00000000 --- a/include/web-socket-js/swfobject.js +++ /dev/null @@ -1,4 +0,0 @@ -/* SWFObject v2.2 - is released under the MIT License -*/ -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/rfc6455 - -(function() { - - if (window.WEB_SOCKET_FORCE_FLASH) { - // Keeps going. - } else if (window.WebSocket) { - return; - } else if (window.MozWebSocket) { - // Firefox. - window.WebSocket = MozWebSocket; - return; - } - - var logger; - if (window.WEB_SOCKET_LOGGER) { - logger = WEB_SOCKET_LOGGER; - } else if (window.console && window.console.log && window.console.error) { - // In some environment, console is defined but console.log or console.error is missing. - logger = window.console; - } else { - logger = {log: function(){ }, error: function(){ }}; - } - - // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. - if (swfobject.getFlashPlayerVersion().major < 10) { - logger.error("Flash Player >= 10.0.0 is required."); - return; - } - if (location.protocol == "file:") { - logger.error( - "WARNING: web-socket-js doesn't work in file:///... URL " + - "unless you set Flash Security Settings properly. " + - "Open the page via Web server i.e. http://..."); - } - - /** - * Our own implementation of WebSocket class using Flash. - * @param {string} url - * @param {array or string} protocols - * @param {string} proxyHost - * @param {int} proxyPort - * @param {string} headers - */ - window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { - var self = this; - self.__id = WebSocket.__nextId++; - WebSocket.__instances[self.__id] = self; - self.readyState = WebSocket.CONNECTING; - self.bufferedAmount = 0; - self.__events = {}; - if (!protocols) { - protocols = []; - } else if (typeof protocols == "string") { - protocols = [protocols]; - } - // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. - // Otherwise, when onopen fires immediately, onopen is called before it is set. - self.__createTask = setTimeout(function() { - WebSocket.__addTask(function() { - self.__createTask = null; - WebSocket.__flash.create( - self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); - }); - }, 0); - }; - - /** - * Send data to the web socket. - * @param {string} data The data to send to the socket. - * @return {boolean} True for success, false for failure. - */ - WebSocket.prototype.send = function(data) { - if (this.readyState == WebSocket.CONNECTING) { - throw "INVALID_STATE_ERR: Web Socket connection has not been established"; - } - // We use encodeURIComponent() here, because FABridge doesn't work if - // the argument includes some characters. We don't use escape() here - // because of this: - // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions - // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't - // preserve all Unicode characters either e.g. "\uffff" in Firefox. - // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require - // additional testing. - var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); - if (result < 0) { // success - return true; - } else { - this.bufferedAmount += result; - return false; - } - }; - - /** - * Close this web socket gracefully. - */ - WebSocket.prototype.close = function() { - if (this.__createTask) { - clearTimeout(this.__createTask); - this.__createTask = null; - this.readyState = WebSocket.CLOSED; - return; - } - if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { - return; - } - this.readyState = WebSocket.CLOSING; - WebSocket.__flash.close(this.__id); - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void - */ - WebSocket.prototype.addEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) { - this.__events[type] = []; - } - this.__events[type].push(listener); - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void - */ - WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) return; - var events = this.__events[type]; - for (var i = events.length - 1; i >= 0; --i) { - if (events[i] === listener) { - events.splice(i, 1); - break; - } - } - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {Event} event - * @return void - */ - WebSocket.prototype.dispatchEvent = function(event) { - var events = this.__events[event.type] || []; - for (var i = 0; i < events.length; ++i) { - events[i](event); - } - var handler = this["on" + event.type]; - if (handler) handler.apply(this, [event]); - }; - - /** - * Handles an event from Flash. - * @param {Object} flashEvent - */ - WebSocket.prototype.__handleEvent = function(flashEvent) { - - if ("readyState" in flashEvent) { - this.readyState = flashEvent.readyState; - } - if ("protocol" in flashEvent) { - this.protocol = flashEvent.protocol; - } - - var jsEvent; - if (flashEvent.type == "open" || flashEvent.type == "error") { - jsEvent = this.__createSimpleEvent(flashEvent.type); - } else if (flashEvent.type == "close") { - jsEvent = this.__createSimpleEvent("close"); - jsEvent.wasClean = flashEvent.wasClean ? true : false; - jsEvent.code = flashEvent.code; - jsEvent.reason = flashEvent.reason; - } else if (flashEvent.type == "message") { - var data = decodeURIComponent(flashEvent.message); - jsEvent = this.__createMessageEvent("message", data); - } else { - throw "unknown event type: " + flashEvent.type; - } - - this.dispatchEvent(jsEvent); - - }; - - WebSocket.prototype.__createSimpleEvent = function(type) { - if (document.createEvent && window.Event) { - var event = document.createEvent("Event"); - event.initEvent(type, false, false); - return event; - } else { - return {type: type, bubbles: false, cancelable: false}; - } - }; - - WebSocket.prototype.__createMessageEvent = function(type, data) { - if (document.createEvent && window.MessageEvent && !window.opera) { - var event = document.createEvent("MessageEvent"); - event.initMessageEvent("message", false, false, data, null, null, window, null); - return event; - } else { - // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. - return {type: type, data: data, bubbles: false, cancelable: false}; - } - }; - - /** - * Define the WebSocket readyState enumeration. - */ - WebSocket.CONNECTING = 0; - WebSocket.OPEN = 1; - WebSocket.CLOSING = 2; - WebSocket.CLOSED = 3; - - // Field to check implementation of WebSocket. - WebSocket.__isFlashImplementation = true; - WebSocket.__initialized = false; - WebSocket.__flash = null; - WebSocket.__instances = {}; - WebSocket.__tasks = []; - WebSocket.__nextId = 0; - - /** - * Load a new flash security policy file. - * @param {string} url - */ - WebSocket.loadFlashPolicyFile = function(url){ - WebSocket.__addTask(function() { - WebSocket.__flash.loadManualPolicyFile(url); - }); - }; - - /** - * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. - */ - WebSocket.__initialize = function() { - - if (WebSocket.__initialized) return; - WebSocket.__initialized = true; - - if (WebSocket.__swfLocation) { - // For backword compatibility. - window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; - } - if (!window.WEB_SOCKET_SWF_LOCATION) { - logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); - return; - } - if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && - !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && - WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { - var swfHost = RegExp.$1; - if (location.host != swfHost) { - logger.error( - "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + - "('" + location.host + "' != '" + swfHost + "'). " + - "See also 'How to host HTML file and SWF file in different domains' section " + - "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + - "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); - } - } - var container = document.createElement("div"); - container.id = "webSocketContainer"; - // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents - // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). - // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash - // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is - // the best we can do as far as we know now. - container.style.position = "absolute"; - if (WebSocket.__isFlashLite()) { - container.style.left = "0px"; - container.style.top = "0px"; - } else { - container.style.left = "-100px"; - container.style.top = "-100px"; - } - var holder = document.createElement("div"); - holder.id = "webSocketFlash"; - container.appendChild(holder); - document.body.appendChild(container); - // See this article for hasPriority: - // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html - swfobject.embedSWF( - WEB_SOCKET_SWF_LOCATION, - "webSocketFlash", - "1" /* width */, - "1" /* height */, - "10.0.0" /* SWF version */, - null, - null, - {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, - null, - function(e) { - if (!e.success) { - logger.error("[WebSocket] swfobject.embedSWF failed"); - } - } - ); - - }; - - /** - * Called by Flash to notify JS that it's fully loaded and ready - * for communication. - */ - WebSocket.__onFlashInitialized = function() { - // We need to set a timeout here to avoid round-trip calls - // to flash during the initialization process. - setTimeout(function() { - WebSocket.__flash = document.getElementById("webSocketFlash"); - WebSocket.__flash.setCallerUrl(location.href); - WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); - for (var i = 0; i < WebSocket.__tasks.length; ++i) { - WebSocket.__tasks[i](); - } - WebSocket.__tasks = []; - }, 0); - }; - - /** - * Called by Flash to notify WebSockets events are fired. - */ - WebSocket.__onFlashEvent = function() { - setTimeout(function() { - try { - // Gets events using receiveEvents() instead of getting it from event object - // of Flash event. This is to make sure to keep message order. - // It seems sometimes Flash events don't arrive in the same order as they are sent. - var events = WebSocket.__flash.receiveEvents(); - for (var i = 0; i < events.length; ++i) { - WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); - } - } catch (e) { - logger.error(e); - } - }, 0); - return true; - }; - - // Called by Flash. - WebSocket.__log = function(message) { - logger.log(decodeURIComponent(message)); - }; - - // Called by Flash. - WebSocket.__error = function(message) { - logger.error(decodeURIComponent(message)); - }; - - WebSocket.__addTask = function(task) { - if (WebSocket.__flash) { - task(); - } else { - WebSocket.__tasks.push(task); - } - }; - - /** - * Test if the browser is running flash lite. - * @return {boolean} True if flash lite is running, false otherwise. - */ - WebSocket.__isFlashLite = function() { - if (!window.navigator || !window.navigator.mimeTypes) { - return false; - } - var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; - if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { - return false; - } - return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; - }; - - if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { - // NOTE: - // This fires immediately if web_socket.js is dynamically loaded after - // the document is loaded. - swfobject.addDomLoadEvent(function() { - WebSocket.__initialize(); - }); - } - -})(); diff --git a/include/websock.js b/include/websock.js index cc82e5a2..71ae36ca 100644 --- a/include/websock.js +++ b/include/websock.js @@ -15,7 +15,7 @@ */ /*jslint browser: true, bitwise: true */ -/*global Util, Base64 */ +/*global Util*/ // Load Flash WebSocket emulator if needed @@ -34,29 +34,22 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) { /* no builtin WebSocket so load web_socket.js */ Websock_native = false; - (function () { - window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() + - "web-socket-js/WebSocketMain.swf"; - if (Util.Engine.trident) { - Util.Debug("Forcing uncached load of WebSocketMain.swf"); - window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); - } - Util.load_scripts(["web-socket-js/swfobject.js", - "web-socket-js/web_socket.js"]); - })(); } - function Websock() { "use strict"; this._websocket = null; // WebSocket object - this._rQ = []; // Receive queue - this._rQi = 0; // Receive queue index - this._rQmax = 10000; // Max receive queue size before compacting - this._sQ = []; // Send queue - this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64' + this._rQi = 0; // Receive queue index + this._rQlen = 0; // Next write position in the receive queue + this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB) + this._rQmax = this._rQbufferSize / 8; + this._sQ = []; // Send queue + // called in init: this._rQ = new Uint8Array(this._rQbufferSize); + this._rQ = null; // Receive queue + + this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64' this.maxBufferedAmount = 200; this._eventHandlers = { @@ -69,6 +62,22 @@ function Websock() { (function () { "use strict"; + + 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 () { @@ -89,7 +98,7 @@ function Websock() { // Receive Queue rQlen: function () { - return this._rQ.length - this._rQi; + return this._rQlen - this._rQi; }, rQpeek8: function () { @@ -108,15 +117,7 @@ function Websock() { this._rQi += num; }, - rQunshift8: function (num) { - if (this._rQi === 0) { - this._rQ.unshift(num); - } else { - this._rQi--; - this._rQ[this._rQi] = num; - } - }, - + // TODO(directxman12): test performance with these vs a DataView rQshift16: function () { return (this._rQ[this._rQi++] << 8) + this._rQ[this._rQi++]; @@ -131,22 +132,29 @@ function Websock() { rQshiftStr: function (len) { if (typeof(len) === 'undefined') { len = this.rQlen(); } - var arr = this._rQ.slice(this._rQi, this._rQi + len); + var arr = new Uint8Array(this._rQ.buffer, this._rQi, len); this._rQi += len; - return String.fromCharCode.apply(null, arr); + return typedArrayToString(arr); }, rQshiftBytes: function (len) { if (typeof(len) === 'undefined') { len = this.rQlen(); } this._rQi += len; - return this._rQ.slice(this._rQi - len, this._rQi); + 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; }, rQslice: function (start, end) { if (end) { - return this._rQ.slice(this._rQi + start, this._rQi + end); + return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); } else { - return this._rQ.slice(this._rQi + start); + return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start); } }, @@ -154,7 +162,7 @@ function Websock() { // 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._rQ.length - this._rQi; // Skip rQlen() function call + var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call if (rQlen < num) { if (goback) { if (this._rQi < goback) { @@ -208,8 +216,12 @@ function Websock() { this._eventHandlers[evt] = handler; }, + _allocate_buffers: function () { + this._rQ = new Uint8Array(this._rQbufferSize); + }, + init: function (protocols, ws_schema) { - this._rQ = []; + this._allocate_buffers(); this._rQi = 0; this._sQ = []; this._websocket = null; @@ -238,35 +250,21 @@ function Websock() { // Default protocols if not specified if (typeof(protocols) === "undefined") { - if (wsbt) { - protocols = ['binary', 'base64']; - } else { - protocols = 'base64'; - } + protocols = 'binary'; + } + + if (Array.isArray(protocols) && protocols.indexOf('binary') > -1) { + protocols = 'binary'; } if (!wsbt) { - if (protocols === 'binary') { - throw new Error('WebSocket binary sub-protocol requested but not supported'); - } + throw new Error("noVNC no longer supports base64 WebSockets. " + + "Please use a browser which supports binary WebSockets."); + } - if (typeof(protocols) === 'object') { - var new_protocols = []; - - for (var i = 0; i < protocols.length; i++) { - if (protocols[i] === 'binary') { - Util.Error('Skipping unsupported WebSocket binary sub-protocol'); - } else { - new_protocols.push(protocols[i]); - } - } - - if (new_protocols.length > 0) { - protocols = new_protocols; - } else { - throw new Error("Only WebSocket binary sub-protocol was requested and is not supported."); - } - } + if (protocols != 'binary') { + throw new Error("noVNC no longer supports base64 WebSockets. Please " + + "use the binary subprotocol instead."); } return protocols; @@ -289,9 +287,16 @@ function Websock() { this._mode = this._websocket.protocol; Util.Info("Server choose sub-protocol: " + this._websocket.protocol); } else { - this._mode = 'base64'; + this._mode = 'binary'; Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol); } + + if (this._mode != 'binary') { + throw new Error("noVNC no longer supports base64 WebSockets. Please " + + "use the binary subprotocol instead."); + + } + this._eventHandlers.open(); Util.Debug("<< WebSock.onopen"); }).bind(this); @@ -321,26 +326,15 @@ function Websock() { // private methods _encode_message: function () { - if (this._mode === 'binary') { - // Put in a binary arraybuffer - return (new Uint8Array(this._sQ)).buffer; - } else { - // base64 encode - return Base64.encode(this._sQ); - } + // Put in a binary arraybuffer + return (new Uint8Array(this._sQ)).buffer; }, _decode_message: function (data) { - if (this._mode === 'binary') { - // push arraybuffer values onto the end - var u8 = new Uint8Array(data); - for (var i = 0; i < u8.length; i++) { - this._rQ.push(u8[i]); - } - } else { - // base64 decode and concat to end - this._rQ = this._rQ.concat(Base64.decode(data, 0)); - } + // push arraybuffer values onto the end + var u8 = new Uint8Array(data); + this._rQ.set(u8, this._rQlen); + this._rQlen += u8.length; }, _recv_message: function (e) { @@ -349,8 +343,26 @@ function Websock() { if (this.rQlen() > 0) { this._eventHandlers.message(); // Compact the receive queue - if (this._rQ.length > this._rQmax) { - this._rQ = this._rQ.slice(this._rQi); + if (this._rQlen == this._rQi) { + this._rQlen = 0; + this._rQi = 0; + } else if (this._rQlen > this._rQmax) { + if (this._rQlen - this._rQi > 0.5 * this._rQbufferSize) { + var old_rQbuffer = this._rQ.buffer; + this._rQbufferSize *= 2; + this._rQmax = this._rQbufferSize / 8; + this._rQ = new Uint8Array(this._rQbufferSize); + this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi)); + } else { + if (this._rQ.copyWithin) { + // Firefox only, ATM + 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; } } else { diff --git a/tests/assertions.js b/tests/assertions.js index 92b11d1f..6c01c4c9 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -5,7 +5,17 @@ chai.use(function (_chai, utils) { var data_cl = obj._drawCtx.getImageData(0, 0, obj._viewportLoc.w, obj._viewportLoc.h).data; // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that var data = new Uint8Array(data_cl); - this.assert(utils.eql(data, target_data), + var same = true; + for (var i = 0; i < obj.length; i++) { + if (data[i] != target_data[i]) { + same = false; + break; + } + } + if (!same) { + console.log("expected data: %o, actual data: %o", target_data, data); + } + this.assert(same, "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}", "expected #{this} not to have displayed the image #{act}", target_data, @@ -15,10 +25,63 @@ chai.use(function (_chai, utils) { _chai.Assertion.addMethod('sent', function (target_data) { var obj = this._obj; var data = obj._websocket._get_sent_data(); - this.assert(utils.eql(data, target_data), + var same = true; + for (var i = 0; i < obj.length; i++) { + if (data[i] != target_data[i]) { + same = false; + break; + } + } + if (!same) { + console.log("expected data: %o, actual data: %o", target_data, data); + } + this.assert(same, "expected #{this} to have sent the data #{exp}, but it actually sent #{act}", "expected #{this} not to have sent the data #{act}", target_data, data); }); + + _chai.Assertion.addProperty('array', function () { + utils.flag(this, 'array', true); + }); + + _chai.Assertion.overwriteMethod('equal', function (_super) { + return function assertArrayEqual(target) { + if (utils.flag(this, 'array')) { + var obj = this._obj; + + var i; + var same = true; + + if (utils.flag(this, 'deep')) { + for (i = 0; i < obj.length; i++) { + if (!utils.eql(obj[i], target[i])) { + same = false; + break; + } + } + + this.assert(same, + "expected #{this} to have elements deeply equal to #{exp}", + "expected #{this} not to have elements deeply equal to #{exp}", + Array.prototype.slice.call(target)); + } else { + for (i = 0; i < obj.length; i++) { + if (obj[i] != target[i]) { + same = false; + break; + } + } + + this.assert(same, + "expected #{this} to have elements equal to #{exp}", + "expected #{this} not to have elements equal to #{exp}", + Array.prototype.slice.call(target)); + } + } else { + _super.apply(this, arguments); + } + }; + }); }); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 5a6ac4a8..f22314da 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1,4 +1,4 @@ -// requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, inflator, des, display +// requires local modules: util, websock, rfb, keyboard, keysym, keysymdef, input, inflator, des, display // requires test modules: fake.websocket, assertions /* jshint expr: true */ var assert = chai.assert; @@ -18,6 +18,25 @@ describe('Remote Frame Buffer Protocol Client', function() { before(FakeWebSocket.replace); after(FakeWebSocket.restore); + before(function () { + this.clock = sinon.useFakeTimers(); + // Use a single set of buffers instead of reallocating to + // speed up tests + var sock = new Websock(); + var rQ = new Uint8Array(sock._rQbufferSize); + + Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers; + Websock.prototype._allocate_buffers = function () { + this._rQ = rQ; + }; + + }); + + after(function () { + Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers; + this.clock.restore(); + }); + describe('Public API Basic Behavior', function () { var client; beforeEach(function () { @@ -1826,7 +1845,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client.connect('host', 8675); client._rfb_state = 'normal'; client._normal_msg = sinon.spy(); - client._sock._websocket._receive_data(Base64.encode([])); + client._sock._websocket._receive_data(new Uint8Array([])); expect(client._normal_msg).to.not.have.been.called; }); @@ -1834,7 +1853,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client.connect('host', 8675); client._rfb_state = 'normal'; client._normal_msg = sinon.spy(); - client._sock._websocket._receive_data(Base64.encode([1, 2, 3])); + client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); expect(client._normal_msg).to.have.been.calledOnce; }); @@ -1842,7 +1861,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client.connect('host', 8675); client._rfb_state = 'ProtocolVersion'; client._init_msg = sinon.spy(); - client._sock._websocket._receive_data(Base64.encode([1, 2, 3])); + client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); expect(client._init_msg).to.have.been.calledOnce; }); diff --git a/tests/test.websock.js b/tests/test.websock.js index 7d242d3e..a81f75da 100644 --- a/tests/test.websock.js +++ b/tests/test.websock.js @@ -1,5 +1,5 @@ -// requires local modules: websock, base64, util -// requires test modules: fake.websocket +// requires local modules: websock, util +// requires test modules: fake.websocket, assertions /* jshint expr: true */ var assert = chai.assert; var expect = chai.expect; @@ -9,13 +9,14 @@ describe('Websock', function() { describe('Queue methods', function () { var sock; - var RQ_TEMPLATE = [0, 1, 2, 3, 4, 5, 6, 7]; + var RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]); beforeEach(function () { sock = new Websock(); - for (var i = RQ_TEMPLATE.length - 1; i >= 0; i--) { - sock.rQunshift8(RQ_TEMPLATE[i]); - } + // skip init + sock._allocate_buffers(); + sock._rQ.set(RQ_TEMPLATE); + sock._rQlen = RQ_TEMPLATE.length; }); describe('rQlen', function () { it('should return the length of the receive queue', function () { @@ -49,14 +50,6 @@ describe('Websock', function() { }); }); - describe('rQunshift8', function () { - it('should place a byte at the front of the queue', function () { - sock.rQunshift8(255); - expect(sock.rQpeek8()).to.equal(255); - expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length + 1); - }); - }); - describe('rQshift16', function () { it('should pop two bytes from the receive queue and return a single number', function () { var bef_len = sock.rQlen(); @@ -84,7 +77,7 @@ describe('Websock', function() { var bef_rQi = sock.get_rQi(); var shifted = sock.rQshiftStr(3); expect(shifted).to.be.a('string'); - expect(shifted).to.equal(String.fromCharCode.apply(null, RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3))); + expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3)))); expect(sock.rQlen()).to.equal(bef_len - 3); }); @@ -99,8 +92,8 @@ describe('Websock', function() { var bef_len = sock.rQlen(); var bef_rQi = sock.get_rQi(); var shifted = sock.rQshiftBytes(3); - expect(shifted).to.be.an.instanceof(Array); - expect(shifted).to.deep.equal(RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3)); + expect(shifted).to.be.an.instanceof(Uint8Array); + expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3)); expect(sock.rQlen()).to.equal(bef_len - 3); }); @@ -123,19 +116,19 @@ describe('Websock', function() { it('should return an array containing the given slice of the receive queue', function () { var sl = sock.rQslice(0, 2); - expect(sl).to.be.an.instanceof(Array); - expect(sl).to.deep.equal(RQ_TEMPLATE.slice(0, 2)); + expect(sl).to.be.an.instanceof(Uint8Array); + expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2)); }); it('should use the rest of the receive queue if no end is given', function () { var sl = sock.rQslice(1); expect(sl).to.have.length(RQ_TEMPLATE.length - 1); - expect(sl).to.deep.equal(RQ_TEMPLATE.slice(1)); + expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1)); }); it('should take the current rQi in to account', function () { sock.set_rQi(1); - expect(sock.rQslice(0, 2)).to.deep.equal(RQ_TEMPLATE.slice(1, 3)); + expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2)); }); }); @@ -257,6 +250,8 @@ describe('Websock', function() { WebSocket.CONNECTING = old_WS.CONNECTING; WebSocket.CLOSING = old_WS.CLOSING; WebSocket.CLOSED = old_WS.CLOSED; + + WebSocket.prototype.binaryType = 'arraybuffer'; }); describe('opening', function () { @@ -265,22 +260,14 @@ describe('Websock', function() { }); it('should open the actual websocket', function () { - sock.open('ws://localhost:8675', 'base64'); - expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'base64'); + sock.open('ws://localhost:8675', 'binary'); + expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary'); }); - it('should fail if we try to use binary but do not support it', function () { - expect(function () { sock.open('ws:///', 'binary'); }).to.throw(Error); + it('should fail if we specify a protocol besides binary', function () { + expect(function () { sock.open('ws:///', 'base64'); }).to.throw(Error); }); - it('should fail if we specified an array with only binary and we do not support it', function () { - expect(function () { sock.open('ws:///', ['binary']); }).to.throw(Error); - }); - - it('should skip binary if we have multiple options for encoding and do not support binary', function () { - sock.open('ws:///', ['binary', 'base64']); - expect(WebSocket).to.have.been.calledWith('ws:///', ['base64']); - }); // it('should initialize the event handlers')? }); @@ -340,16 +327,15 @@ describe('Websock', function() { expect(sock._recv_message).to.have.been.calledOnce; }); - it('should copy the mode over upon opening', function () { - sock._websocket.protocol = 'cheese'; - sock._websocket.onopen(); - expect(sock._mode).to.equal('cheese'); + it('should fail if a protocol besides binary is requested', function () { + sock._websocket.protocol = 'base64'; + expect(sock._websocket.onopen).to.throw(Error); }); - it('should assume base64 if no protocol was available on opening', function () { + it('should assume binary if no protocol was available on opening', function () { sock._websocket.protocol = null; sock._websocket.onopen(); - expect(sock._mode).to.equal('base64'); + expect(sock._mode).to.equal('binary'); }); it('should call the open event handler on opening', function () { @@ -377,13 +363,7 @@ describe('Websock', function() { var sock; beforeEach(function () { sock = new Websock(); - }); - - it('should support decoding base64 string data to add it to the receive queue', function () { - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; - sock._recv_message(msg); - expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03'); + sock._allocate_buffers(); }); it('should support adding binary Uint8Array data to the receive queue', function () { @@ -395,16 +375,16 @@ describe('Websock', function() { it('should call the message event handler if present', function () { sock._eventHandlers.message = sinon.spy(); - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([1, 2, 3]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); expect(sock._eventHandlers.message).to.have.been.calledOnce; }); it('should not call the message event handler if there is nothing in the receive queue', function () { sock._eventHandlers.message = sinon.spy(); - var msg = { data: Base64.encode([]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); expect(sock._eventHandlers.message).not.to.have.been.called; }); @@ -412,21 +392,22 @@ describe('Websock', function() { it('should compact the receive queue', function () { // NB(sross): while this is an internal implementation detail, it's important to // test, otherwise the receive queue could become very large very quickly - sock._rQ = [0, 1, 2, 3, 4, 5]; + sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]); + sock._rQlen = 6; sock.set_rQi(6); sock._rQmax = 3; - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([1, 2, 3]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); - expect(sock._rQ.length).to.equal(3); + expect(sock._rQlen).to.equal(3); expect(sock.get_rQi()).to.equal(0); }); it('should call the error event handler on an exception', function () { sock._eventHandlers.error = sinon.spy(); sock._eventHandlers.message = sinon.stub().throws(); - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([1, 2, 3]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); expect(sock._eventHandlers.error).to.have.been.calledOnce; }); @@ -455,26 +436,5 @@ describe('Websock', function() { expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); }); }); - - describe('as Base64 data', function () { - var sock; - beforeEach(function () { - sock = new Websock(); - sock.open('ws://', 'base64'); - sock._websocket._open(); - }); - - it('should convert the send queue into a Base64-encoded string', function () { - sock._sQ = [1, 2, 3]; - expect(sock._encode_message()).to.equal(Base64.encode([1, 2, 3])); - }); - - it('should properly pass the encoded data off to the actual WebSocket', function () { - sock.send([1, 2, 3]); - expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); - }); - - }); - }); }); From d1800d0960ad9c3d2ff20c990f5886d2edf7f514 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Thu, 28 May 2015 15:27:40 -0400 Subject: [PATCH 06/10] Avoid Creating Small Objects Frequently Creating lots of small objects frequently can drastically decrease performance. This commit introduces three fixes which avoid this: - Use a preallocated palette and indexed-to-rgb destination Typed Array (the destination typed array is currently allocated at `4 * width * height`). - Inline `getTightCLength`, which returned a two-item array. - Pass RGBX data directly in a Typed Array to the Display, which avoids an extra loop, and only creates a new Typed Array View, instead of a whole new ArrayBuffer. --- include/display.js | 28 ++++++ include/inflator.js | 2 +- include/rfb.js | 212 ++++++++++++++++++++++++++++++++------------ 3 files changed, 183 insertions(+), 59 deletions(-) diff --git a/include/display.js b/include/display.js index 8994856b..418b431d 100644 --- a/include/display.js +++ b/include/display.js @@ -15,6 +15,14 @@ var Display; (function () { "use strict"; + var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false; + try { + new ImageData(new Uint8ClampedArray(1), 1, 1); + SUPPORTS_IMAGEDATA_CONSTRUCTOR = true; + } catch (ex) { + // ignore failure + } + Display = function (defaults) { this._drawCtx = null; this._c_forceCanvas = false; @@ -435,6 +443,10 @@ var Display; } }, + blitRgbxImage: function (x, y, width, height, arr, offset) { + this._rgbxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); + }, + blitStringImage: function (str, x, y) { var img = new Image(); img.onload = function () { @@ -612,6 +624,19 @@ var Display; this._drawCtx.putImageData(img, x - vx, y - vy); }, + _rgbxImageData: function (x, y, vx, vy, width, height, arr, offset) { + // NB(directxman12): arr must be an Type Array view + // NB(directxman12): this only works + var img; + if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) { + img = new ImageData(new Uint8ClampedArray(arr.buffer, 0, width * height * 4), width, height); + } else { + img = this._drawCtx.createImageData(width, height); + img.data.set(new Uint8ClampedArray(arr.buffer, 0, width * height * 4)); + } + this._drawCtx.putImageData(img, x - vx, y - vy); + }, + _cmapImageData: function (x, y, vx, vy, width, height, arr, offset) { var img = this._drawCtx.createImageData(width, height); var data = img.data; @@ -643,6 +668,9 @@ var Display; case 'blitRgb': this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0); break; + case 'blitRgbx': + this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0); + break; case 'img': if (a.img.complete) { this.drawImage(a.img, a.x, a.y); diff --git a/include/inflator.js b/include/inflator.js index 68f85cbd..a9c75a62 100644 --- a/include/inflator.js +++ b/include/inflator.js @@ -2386,7 +2386,7 @@ var Inflate = function () { Inflate.prototype = { inflate: function (data, flush) { - this.strm.input = new Uint8Array(data); + this.strm.input = data; this.strm.avail_in = this.strm.input.length; this.strm.next_in = 0; this.strm.next_out = 0; diff --git a/include/rfb.js b/include/rfb.js index 113d9419..e2dd42f5 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -91,7 +91,9 @@ var RFB; this._fb_width = 0; this._fb_height = 0; this._fb_name = ""; - this._dest_buff = null; + + this._destBuff = null; + this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel) this._rre_chunk_sz = 100; @@ -1665,43 +1667,77 @@ var RFB; return uncompressed; }.bind(this); - var indexedToRGB = function (data, numColors, palette, width, height) { + 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._dest_buff; - var x, y, dp, sp; - if (numColors === 2) { - var w = Math.floor((width + 7) / 8); - var w1 = Math.floor(width / 8); + var dest = this._destBuff; + var w = Math.floor((width + 7) / 8); + var w1 = Math.floor(width / 8); - for (y = 0; y < height; y++) { - var b; - for (x = 0; x < w1; x++) { - for (b = 7; b >= 0; b--) { - dp = (y * width + x * 8 + 7 - b) * 3; - sp = (data[y * w + x] >> 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--) { - dp = (y * width + x * 8 + 7 - b) * 3; - sp = (data[y * w + x] >> b & 1) * 3; + /*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]; } } - } else { - var total = width * height * 3; - for (var i = 0, j = 0; i < total; i += 3, j++) { - sp = data[j] * 3; - dest[i] = palette[sp]; - dest[i + 1] = palette[sp + 1]; - dest[i + 2] = palette[sp + 2]; + + 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 (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; + } + } + + 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 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; @@ -1709,7 +1745,8 @@ var RFB; var rQ = this._sock.get_rQ(); var rQi = this._sock.get_rQi(); - var cmode, clength, data; + var cmode, data; + var cl_header, cl_data; var handlePalette = function () { var numColors = rQ[rQi + 2] + 1; @@ -1722,37 +1759,69 @@ var RFB; var raw = false; if (rowSize * this._FBU.height < 12) { raw = true; - clength = [0, rowSize * this._FBU.height]; + cl_header = 0; + cl_data = rowSize * this._FBU.height; + //clength = [0, rowSize * this._FBU.height]; } else { - clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize, - 3 + paletteSize + 3)); + // 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 += clength[0] + clength[1]; + 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.rQskipBytes(clength[0]); + //var palette = this._sock.rQshiftBytes(paletteSize); + this._sock.rQshiftTo(this._paletteBuff, paletteSize); + this._sock.rQskipBytes(cl_header); if (raw) { - data = this._sock.rQshiftBytes(clength[1]); + data = this._sock.rQshiftBytes(cl_data); } else { - data = decompress(this._sock.rQshiftBytes(clength[1])); + data = decompress(this._sock.rQshiftBytes(cl_data)); } // Convert indexed (palette based) image data to RGB - var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height); + var rgbx; + if (numColors == 2) { + rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height); + + /*this._display.renderQ_push({ + 'type': 'blitRgbx', + 'data': rgbx, + 'x': this._FBU.x, + 'y': this._FBU.y, + 'width': this._FBU.width, + 'height': this._FBU.height + });*/ + this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0); + } else { + rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height); + + /*this._display.renderQ_push({ + 'type': 'blitRgbx', + 'data': rgbx, + 'x': this._FBU.x, + 'y': this._FBU.y, + 'width': this._FBU.width, + 'height': this._FBU.height + });*/ + this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0); + } - this._display.renderQ_push({ - 'type': 'blitRgb', - 'data': rgb, - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - }); return true; }.bind(this); @@ -1762,20 +1831,34 @@ var RFB; var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth; if (uncompressedSize < 12) { raw = true; - clength = [0, uncompressedSize]; + cl_header = 0; + cl_data = uncompressedSize; } else { - clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); + // 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 + clength[0] + clength[1]; + 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 + clength[0]); + this._sock.rQshiftBytes(1 + cl_header); if (raw) { - data = this._sock.rQshiftBytes(clength[1]); + data = this._sock.rQshiftBytes(cl_data); } else { - data = decompress(this._sock.rQshiftBytes(clength[1])); + data = decompress(this._sock.rQshiftBytes(cl_data)); } this._display.renderQ_push({ @@ -1846,15 +1929,28 @@ var RFB; break; case "png": case "jpeg": - clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); - this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data + // 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 + clength[0]); // shift off clt + compact length + this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length var img = new Image(); img.src = "data: image/" + cmode + - RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1])); + RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data)); this._display.renderQ_push({ 'type': 'img', 'img': img, @@ -1897,7 +1993,7 @@ var RFB; handle_FB_resize: function () { this._fb_width = this._FBU.width; this._fb_height = this._FBU.height; - this._dest_buff = new Uint8Array(this._fb_width * this._fb_height * 4); + 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(); From 9ff86fb718477515ede2d6457f06643935d76bcd Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Thu, 28 May 2015 15:28:30 -0400 Subject: [PATCH 07/10] Use Typed Arrays for the send queue This commit converts the send queue to use typed arrays, and converts message creation functions in 'rfb.js' to create messages directly into the socket's send queue. This commit also removes the separate mouse array, which is no longer needed. --- include/rfb.js | 256 +++++++++++++++++++++++----------------- include/websock.js | 20 ++-- tests/assertions.js | 10 +- tests/fake.websocket.js | 9 +- tests/test.rfb.js | 180 ++++++++++++++-------------- tests/test.websock.js | 18 +-- 6 files changed, 277 insertions(+), 216 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index e2dd42f5..b8615af8 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -259,14 +259,14 @@ var RFB; if (this._rfb_state !== 'normal' || this._view_only) { return false; } Util.Info("Sending Ctrl-Alt-Del"); - var arr = []; - arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 0)); - arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 0)); - arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 0)); - this._sock.send(arr); + RFB.messages.keyEvent(this._sock, XK_Control_L, 1); + RFB.messages.keyEvent(this._sock, XK_Alt_L, 1); + RFB.messages.keyEvent(this._sock, XK_Delete, 1); + RFB.messages.keyEvent(this._sock, XK_Delete, 0); + RFB.messages.keyEvent(this._sock, XK_Alt_L, 0); + RFB.messages.keyEvent(this._sock, XK_Control_L, 0); + + this._sock.flush(); }, xvpOp: function (ver, op) { @@ -292,21 +292,22 @@ var RFB; // followed by an up key. sendKey: function (code, down) { if (this._rfb_state !== "normal" || this._view_only) { return false; } - var arr = []; if (typeof down !== 'undefined') { Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); - arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0)); + RFB.messages.keyEvent(this._sock, code, down ? 1 : 0); } else { Util.Info("Sending key code (down + up): " + code); - arr = arr.concat(RFB.messages.keyEvent(code, 1)); - arr = arr.concat(RFB.messages.keyEvent(code, 0)); + RFB.messages.keyEvent(this._sock, code, 1); + RFB.messages.keyEvent(this._sock, code, 0); } - this._sock.send(arr); + + this._sock.flush(); }, clipboardPasteFrom: function (text) { if (this._rfb_state !== 'normal') { return; } - this._sock.send(RFB.messages.clientCutText(text)); + RFB.messages.clientCutText(this._sock, text); + this._sock.flush(); }, setDesktopSize: function (width, height) { @@ -572,16 +573,10 @@ var RFB; } }, - _checkEvents: function () { - if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) { - this._sock.send(this._mouse_arr); - this._mouse_arr = []; - } - }, - _handleKeyPress: function (keysym, down) { if (this._view_only) { return; } // View only, skip keyboard, events - this._sock.send(RFB.messages.keyEvent(keysym, down)); + RFB.messages.keyEvent(this._sock, keysym, down); + this._sock.flush(); }, _handleMouseButton: function (x, y, down, bmask) { @@ -605,10 +600,8 @@ var RFB; if (this._view_only) { return; } // View only, skip mouse events - this._mouse_arr = this._mouse_arr.concat( - RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); - this._sock.send(this._mouse_arr); - this._mouse_arr = []; + if (this._rfb_state !== "normal") { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); }, _handleMouseMove: function (x, y) { @@ -625,10 +618,8 @@ var RFB; if (this._view_only) { return; } // View only, skip mouse events - this._mouse_arr = this._mouse_arr.concat( - RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); - - this._checkEvents(); + if (this._rfb_state !== "normal") { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); }, // Message Handlers @@ -895,7 +886,7 @@ var RFB; /* Screen size */ this._fb_width = this._sock.rQshift16(); this._fb_height = this._sock.rQshift16(); - this._dest_buff = new Uint8Array(this._fb_width * this._fb_height * 4); + this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); /* PIXEL_FORMAT */ var bpp = this._sock.rQshift8(); @@ -991,18 +982,13 @@ var RFB; this._fb_depth = 1; } - var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color); - response = response.concat( - RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color)); - response = response.concat( - RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), - this._fb_width, this._fb_height)); + 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.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); this._timing.fbu_rt_start = (new Date()).getTime(); this._timing.pixels = 0; - this._sock.send(response); - - this._checkEvents(); + this._sock.flush(); if (this._encrypt) { this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name); @@ -1104,8 +1090,8 @@ var RFB; case 0: // FramebufferUpdate var ret = this._framebufferUpdate(); if (ret) { - this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), - this._fb_width, this._fb_height)); + RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); + this._sock.flush(); } return ret; @@ -1279,64 +1265,111 @@ var RFB; // Class Methods RFB.messages = { - keyEvent: function (keysym, down) { - var arr = [4]; - arr.push8(down); - arr.push16(0); - arr.push32(keysym); - return arr; + 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; }, - pointerEvent: function (x, y, mask) { - var arr = [5]; // msg-type - arr.push8(mask); - arr.push16(x); - arr.push16(y); - return arr; + 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; }, // TODO(directxman12): make this unicode compatible? - clientCutText: function (text) { - var arr = [6]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - arr.push32(text.length); + 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++) { - arr.push(text.charCodeAt(i)); + buff[offset + 8 + i] = text.charCodeAt(i); } - return arr; + sock._sQlen += 8 + n; }, - pixelFormat: function (bpp, depth, true_color) { - var arr = [0]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding + pixelFormat: function (sock, bpp, depth, true_color) { + var buff = sock._sQ; + var offset = sock._sQlen; - arr.push8(bpp * 8); // bits-per-pixel - arr.push8(depth * 8); // depth - arr.push8(0); // little-endian - arr.push8(true_color ? 1 : 0); // true-color + buff[offset] = 0; // msg-type - arr.push16(255); // red-max - arr.push16(255); // green-max - arr.push16(255); // blue-max - arr.push8(16); // red-shift - arr.push8(8); // green-shift - arr.push8(0); // blue-shift + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - return arr; + 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; }, - clientEncodings: function (encodings, local_cursor, true_color) { - var i, encList = []; + 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"); @@ -1344,23 +1377,25 @@ var RFB; // TODO: remove this when we have tight+non-true-color Util.Warn("Skipping tight as it is only supported with true color"); } else { - encList.push(encodings[i][1]); + 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++; } } - var arr = [2]; // msg-type - arr.push8(0); // padding + buff[offset + 2] = cnt >> 8; + buff[offset + 3] = cnt; - arr.push16(encList.length); // encoding count - for (i = 0; i < encList.length; i++) { - arr.push32(encList[i]); - } - - return arr; + sock._sQlen += j - offset; }, - fbUpdateRequests: function (cleanDirty, fb_width, fb_height) { - var arr = []; + fbUpdateRequests: function (sock, cleanDirty, fb_width, fb_height) { + var offsetIncrement = 0; var cb = cleanDirty.cleanBox; var w, h; @@ -1368,7 +1403,7 @@ var RFB; w = typeof cb.w === "undefined" ? fb_width : cb.w; h = typeof cb.h === "undefined" ? fb_height : cb.h; // Request incremental for clean box - arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h)); + RFB.messages.fbUpdateRequest(sock, 1, cb.x, cb.y, w, h); } for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) { @@ -1376,24 +1411,33 @@ var RFB; // Force all (non-incremental) for dirty box w = typeof db.w === "undefined" ? fb_width : db.w; h = typeof db.h === "undefined" ? fb_height : db.h; - arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h)); + RFB.messages.fbUpdateRequest(sock, 0, db.x, db.y, w, h); } - - return arr; }, - fbUpdateRequest: function (incremental, x, y, w, h) { + 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; } - var arr = [3]; // msg-type - arr.push8(incremental); - arr.push16(x); - arr.push16(y); - arr.push16(w); - arr.push16(h); + buff[offset] = 3; // msg-type + buff[offset + 1] = incremental; - return arr; + 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; } }; diff --git a/include/websock.js b/include/websock.js index 71ae36ca..61d94672 100644 --- a/include/websock.js +++ b/include/websock.js @@ -45,10 +45,14 @@ function Websock() { this._rQlen = 0; // Next write position in the receive queue this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB) this._rQmax = this._rQbufferSize / 8; - this._sQ = []; // Send queue // called in init: this._rQ = new Uint8Array(this._rQbufferSize); this._rQ = null; // Receive queue + this._sQbufferSize = 1024 * 10; // 10 KiB + // called in init: this._sQ = new Uint8Array(this._sQbufferSize); + this._sQlen = 0; + this._sQ = null; // Send queue + this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64' this.maxBufferedAmount = 200; @@ -183,9 +187,9 @@ function Websock() { } if (this._websocket.bufferedAmount < this.maxBufferedAmount) { - if (this._sQ.length > 0) { + if (this._sQlen > 0) { this._websocket.send(this._encode_message()); - this._sQ = []; + this._sQlen = 0; } return true; @@ -197,8 +201,9 @@ function Websock() { }, send: function (arr) { - this._sQ = this._sQ.concat(arr); - return this.flush(); + this._sQ.set(arr, this._sQlen); + this._sQlen += arr.length; + return this.flush(); }, send_string: function (str) { @@ -218,12 +223,12 @@ function Websock() { _allocate_buffers: function () { this._rQ = new Uint8Array(this._rQbufferSize); + this._sQ = new Uint8Array(this._sQbufferSize); }, init: function (protocols, ws_schema) { this._allocate_buffers(); this._rQi = 0; - this._sQ = []; this._websocket = null; // Check for full typed array support @@ -327,7 +332,8 @@ function Websock() { // private methods _encode_message: function () { // Put in a binary arraybuffer - return (new Uint8Array(this._sQ)).buffer; + // according to the spec, you can send ArrayBufferViews with the send method + return new Uint8Array(this._sQ.buffer, 0, this._sQlen); }, _decode_message: function (data) { diff --git a/tests/assertions.js b/tests/assertions.js index 6c01c4c9..930e1460 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -24,6 +24,12 @@ chai.use(function (_chai, utils) { _chai.Assertion.addMethod('sent', function (target_data) { var obj = this._obj; + obj.inspect = function () { + var res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen), + _sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) }; + res.prototype = obj; + return res; + }; var data = obj._websocket._get_sent_data(); var same = true; for (var i = 0; i < obj.length; i++) { @@ -38,8 +44,8 @@ chai.use(function (_chai, utils) { this.assert(same, "expected #{this} to have sent the data #{exp}, but it actually sent #{act}", "expected #{this} not to have sent the data #{act}", - target_data, - data); + Array.prototype.slice.call(target_data), + Array.prototype.slice.call(data)); }); _chai.Assertion.addProperty('array', function () { diff --git a/tests/fake.websocket.js b/tests/fake.websocket.js index 749c0eaf..21012059 100644 --- a/tests/fake.websocket.js +++ b/tests/fake.websocket.js @@ -51,14 +51,9 @@ var FakeWebSocket; }, _get_sent_data: function () { - var arr = []; - for (var i = 0; i < this.bufferedAmount; i++) { - arr[i] = this._send_queue[i]; - } - + var res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount); this.bufferedAmount = 0; - - return arr; + return res; }, _open: function (data) { diff --git a/tests/test.rfb.js b/tests/test.rfb.js index f22314da..961d9eb5 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -23,10 +23,12 @@ describe('Remote Frame Buffer Protocol Client', function() { // Use a single set of buffers instead of reallocating to // speed up tests var sock = new Websock(); + var _sQ = new Uint8Array(sock._sQbufferSize); var rQ = new Uint8Array(sock._rQbufferSize); Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers; Websock.prototype._allocate_buffers = function () { + this._sQ = _sQ; this._rQ = rQ; }; @@ -124,34 +126,34 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { - var expected = []; - expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 0)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 0)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 0)); + var expected = {_sQ: new Uint8Array(48), _sQlen: 0}; + RFB.messages.keyEvent(expected, 0xFFE3, 1); + RFB.messages.keyEvent(expected, 0xFFE9, 1); + RFB.messages.keyEvent(expected, 0xFFFF, 1); + RFB.messages.keyEvent(expected, 0xFFFF, 0); + RFB.messages.keyEvent(expected, 0xFFE9, 0); + RFB.messages.keyEvent(expected, 0xFFE3, 0); client.sendCtrlAltDel(); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the keys if we are not in a normal state', function () { client._rfb_state = "broken"; client.sendCtrlAltDel(); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the keys if we are set as view_only', function () { client._view_only = true; client.sendCtrlAltDel(); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -160,34 +162,36 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should send a single key with the given code and state (down = true)', function () { - var expected = RFB.messages.keyEvent(123, 1); + var expected = {_sQ: new Uint8Array(8), _sQlen: 0}; + RFB.messages.keyEvent(expected, 123, 1); client.sendKey(123, true); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should send both a down and up event if the state is not specified', function () { - var expected = RFB.messages.keyEvent(123, 1); - expected = expected.concat(RFB.messages.keyEvent(123, 0)); + var expected = {_sQ: new Uint8Array(16), _sQlen: 0}; + RFB.messages.keyEvent(expected, 123, 1); + RFB.messages.keyEvent(expected, 123, 0); client.sendKey(123); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the key if we are not in a normal state', function () { client._rfb_state = "broken"; client.sendKey(123); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the key if we are set as view_only', function () { client._view_only = true; client.sendKey(123); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -196,21 +200,22 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should send the given text in a paste event', function () { - var expected = RFB.messages.clientCutText('abc'); + var expected = {_sQ: new Uint8Array(11), _sQlen: 0}; + RFB.messages.clientCutText(expected, 'abc'); client.clipboardPasteFrom('abc'); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the text if we are not in a normal state', function () { client._rfb_state = "broken"; client.clipboardPasteFrom('abc'); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -219,7 +224,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; client._supportsSetDesktopSize = true; @@ -240,19 +245,19 @@ describe('Remote Frame Buffer Protocol Client', function() { expected.push32(0); // flags client.setDesktopSize(1, 2); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () { client._supportsSetDesktopSize = false; client.setDesktopSize(1,2); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the request if we are not in a normal state', function () { client._rfb_state = "broken"; client.setDesktopSize(1,2); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -261,7 +266,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; client._rfb_xvp_ver = 1; @@ -269,27 +274,27 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send the shutdown signal on #xvpShutdown', function () { client.xvpShutdown(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x02]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02])); }); it('should send the reboot signal on #xvpReboot', function () { client.xvpReboot(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x03]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03])); }); it('should send the reset signal on #xvpReset', function () { client.xvpReset(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x04]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04])); }); it('should support sending arbitrary XVP operations via #xvpOp', function () { client.xvpOp(1, 7); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x07]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x07])); }); it('should not send XVP operations with higher versions than we support', function () { expect(client.xvpOp(2, 7)).to.be.false; - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); }); @@ -502,7 +507,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); }); it('should interpret version 003.003 as version 3.3', function () { @@ -559,7 +564,7 @@ describe('Remote Frame Buffer Protocol Client', function() { send_ver('000.000', client); expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); expect(sent_data).to.have.length(250); send_ver('003.008', client); @@ -582,7 +587,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expected[i] = expected_str.charCodeAt(i); } - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); it('should transition to the Security state on successful negotiation', function () { @@ -615,7 +620,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var auth_schemes = [2, 1, 2]; client._sock._websocket._receive_data(auth_schemes); expect(client._rfb_auth_scheme).to.equal(2); - expect(client._sock).to.have.sent([2]); + expect(client._sock).to.have.sent(new Uint8Array([2])); }); it('should fail if there are no supported schemes for versions >= 3.7', function () { @@ -721,7 +726,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._receive_data(new Uint8Array(challenge)); var des_pass = RFB.genDES('passwd', challenge); - expect(client._sock).to.have.sent(des_pass); + expect(client._sock).to.have.sent(new Uint8Array(des_pass)); }); it('should transition to SecurityResult immediately after sending the password', function () { @@ -778,7 +783,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var expected = [22, 4, 6]; // auth selection, len user, len target for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); } - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); }); @@ -826,14 +831,14 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should choose the notunnel tunnel type', function () { send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client); - expect(client._sock).to.have.sent([0, 0, 0, 0]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0])); }); it('should continue to sub-auth negotiation after tunnel negotiation', function () { send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client); client._sock._websocket._get_sent_data(); // skip the tunnel choice here send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); expect(client._rfb_state).to.equal('SecurityResult'); }); @@ -849,7 +854,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should accept the "no auth" auth type and transition to SecurityResult', function () { client._rfb_tightvnc = true; send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); expect(client._rfb_state).to.equal('SecurityResult'); }); @@ -857,7 +862,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._rfb_tightvnc = true; client._negotiate_std_vnc_auth = sinon.spy(); send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 2]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2])); expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; expect(client._rfb_auth_scheme).to.equal(2); }); @@ -921,13 +926,13 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send 1 if we are in shared mode', function () { client.set_shared(true); client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent([1]); + expect(client._sock).to.have.sent(new Uint8Array([1])); }); it('should send 0 if we are not in shared mode', function () { client.set_shared(false); client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent([0]); + expect(client._sock).to.have.sent(new Uint8Array([0])); }); }); @@ -1064,21 +1069,16 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should reply with the pixel format, client encodings, and initial update request', function () { client.set_true_color(true); client.set_local_cursor(false); - var expected = RFB.messages.pixelFormat(4, 3, true); - expected = expected.concat(RFB.messages.clientEncodings(client._encodings, false, true)); + // we skip the cursor encoding + var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)), _sQlen: 0}; + RFB.messages.pixelFormat(expected, 4, 3, true); + RFB.messages.clientEncodings(expected, client._encodings, false, true); var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 27, h: 32 } ] }; - expected = expected.concat(RFB.messages.fbUpdateRequests(expected_cdr, 27, 32)); + RFB.messages.fbUpdateRequests(expected, expected_cdr, 27, 32); send_server_init({ width: 27, height: 32 }, client); - expect(client._sock).to.have.sent(expected); - }); - - it('should check for sending mouse events', function () { - // be lazy with our checking so we don't have to check through the whole sent buffer - sinon.spy(client, '_checkEvents'); - send_server_init({}, client); - expect(client._checkEvents).to.have.been.calledOnce; + expect(client._sock).to.have.sent(expected._sQ); }); it('should transition to the "normal" state', function () { @@ -1161,14 +1161,15 @@ describe('Remote Frame Buffer Protocol Client', function() { } it('should send an update request if there is sufficient data', function () { + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0}; var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; - var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); client._framebufferUpdate = function () { return true; }; client._sock._websocket._receive_data(new Uint8Array([0])); - expect(client._sock).to.have.sent(expected_msg); + expect(client._sock).to.have.sent(expected_msg._sQ); }); it('should not send an update request if we need more data', function () { @@ -1177,9 +1178,10 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should resume receiving an update if we previously did not have enough data', function () { + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0}; var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; - var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); // just enough to set FBU.rects client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3])); @@ -1188,7 +1190,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._framebufferUpdate = function () { return true; }; // we magically have enough data // 247 should *not* be used as the message type here client._sock._websocket._receive_data(new Uint8Array([247])); - expect(client._sock).to.have.sent(expected_msg); + expect(client._sock).to.have.sent(expected_msg._sQ); }); it('should parse out information from a header before any actual data comes in', function () { @@ -1710,58 +1712,61 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock.send = sinon.spy(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'flush'); client._rfb_state = 'normal'; }); it('should not send button messages in view-only mode', function () { client._view_only = true; client._mouse._onMouseButton(0, 0, 1, 0x001); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { client._view_only = true; client._mouse._onMouseMove(0, 0); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should send a pointer event on mouse button presses', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a mask of 1 on mousedown', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 0, 10, 12, 0x001); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a mask of 0 on mouseup', function () { client._mouse_buttonMask = 0x001; client._mouse._onMouseButton(10, 12, 0, 0x001); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x000); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a pointer event on mouse movement', function () { client._mouse._onMouseMove(10, 12); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should set the button mask so that future mouse movements use it', function () { client._mouse._onMouseButton(10, 12, 1, 0x010); - client._sock.send = sinon.spy(); client._mouse._onMouseMove(13, 9); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(13, 9, 0x010); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010); + RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); // NB(directxman12): we don't need to test not sending messages in @@ -1772,13 +1777,13 @@ describe('Remote Frame Buffer Protocol Client', function() { client._viewportDragging = true; client._display.viewportChangePos = sinon.spy(); client._mouse._onMouseMove(13, 9); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send button messages when initiating viewport dragging', function () { client._viewportDrag = true; client._mouse._onMouseButton(13, 9, 0x001); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should be initiate viewport dragging on a button down event, if enabled', function () { @@ -1814,20 +1819,23 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock.send = sinon.spy(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'flush'); }); it('should send a key message on a key press', function () { client._keyboard._onKeyPress(1234, 1); - expect(client._sock.send).to.have.been.calledOnce; - var key_msg = RFB.messages.keyEvent(1234, 1); - expect(client._sock.send).to.have.been.calledWith(key_msg); + var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0}; + RFB.messages.keyEvent(key_msg, 1234, 1); + expect(client._sock).to.have.sent(key_msg._sQ); }); it('should not send messages in view-only mode', function () { client._view_only = true; client._keyboard._onKeyPress(1234, 1); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); diff --git a/tests/test.websock.js b/tests/test.websock.js index a81f75da..14d57832 100644 --- a/tests/test.websock.js +++ b/tests/test.websock.js @@ -173,7 +173,8 @@ describe('Websock', function() { it('should actually send on the websocket if the websocket does not have too much buffered', function () { sock.maxBufferedAmount = 10; sock._websocket.bufferedAmount = 8; - sock._sQ = [1, 2, 3]; + sock._sQ = new Uint8Array([1, 2, 3]); + sock._sQlen = 3; var encoded = sock._encode_message(); sock.flush(); @@ -189,7 +190,7 @@ describe('Websock', function() { }); it('should not call send if we do not have anything queued up', function () { - sock._sQ = []; + sock._sQlen = 0; sock.maxBufferedAmount = 10; sock._websocket.bufferedAmount = 8; @@ -215,7 +216,7 @@ describe('Websock', function() { it('should add to the send queue', function () { sock.send([1, 2, 3]); var sq = sock.get_sQ(); - expect(sock.get_sQ().slice(sq.length - 3)).to.deep.equal([1, 2, 3]); + expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3])); }); it('should call flush', function () { @@ -425,15 +426,16 @@ describe('Websock', function() { sock._websocket._open(); }); - it('should convert the send queue into an ArrayBuffer', function () { - sock._sQ = [1, 2, 3]; - var res = sock._encode_message(); // An ArrayBuffer - expect(new Uint8Array(res)).to.deep.equal(new Uint8Array(res)); + it('should only send the send queue up to the send queue length', function () { + sock._sQ = new Uint8Array([1, 2, 3, 4, 5]); + sock._sQlen = 3; + var res = sock._encode_message(); + expect(res).to.array.equal(new Uint8Array([1, 2, 3])); }); it('should properly pass the encoded data off to the actual WebSocket', function () { sock.send([1, 2, 3]); - expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); + expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3])); }); }); }); From 07f514d887c27369f7675296a2cb1cb6d664cd91 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Thu, 28 May 2015 15:07:43 -0400 Subject: [PATCH 08/10] Allow the use of the PhantomJS remote debugger This commit adds the '--debugger ' option, which triggers the PhantomJS remote debugger. The initial output of the terminal when running the debugger gives more information on how to use it. --- tests/run_from_console.casper.js | 14 +++++++++++++- tests/run_from_console.js | 3 ++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/run_from_console.casper.js b/tests/run_from_console.casper.js index 57ed2be2..6a738a3e 100644 --- a/tests/run_from_console.casper.js +++ b/tests/run_from_console.casper.js @@ -15,7 +15,19 @@ var casper_opts = { } }; -var provide_emitter = function(file_paths) { +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); diff --git a/tests/run_from_console.js b/tests/run_from_console.js index 2a5bb70b..f5c5bb4b 100755 --- a/tests/run_from_console.js +++ b/tests/run_from_console.js @@ -20,6 +20,7 @@ program .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) { @@ -202,7 +203,7 @@ if (!program.outputHtml && !program.generateHtml) { .write("\n"); //console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name); - var provider = prov.provide_emitter(file_paths); + var provider = prov.provide_emitter(file_paths, program.debugger); provider.on('test_ready', function(test_json) { console.log(''); From b0b5fc55e1fe5c575855104e9ce6df79329a8db7 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Thu, 28 May 2015 15:09:31 -0400 Subject: [PATCH 09/10] Fix multi-line assertion messages in test runner This commit prevents multi-line error messages from being truncated in the local test runner ('tests/run_from_console.js'). --- tests/run_from_console.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/run_from_console.js b/tests/run_from_console.js index f5c5bb4b..371e861a 100755 --- a/tests/run_from_console.js +++ b/tests/run_from_console.js @@ -250,6 +250,24 @@ if (!program.outputHtml && !program.generateHtml) { 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; @@ -281,7 +299,7 @@ if (!program.outputHtml && !program.generateHtml) { cursor.magenta(); console.log('- failed: '+node.text+test_json.replay); cursor.red(); - console.log(' '+node.error.split("\n")[0]); // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too + console.log(' '+extract_error_lines(node.error)); cursor.reset(); console.log(''); } From f00193e08f7c55a7b0b55556ef96b10b41dd3bc3 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 2 Jun 2015 15:32:14 -0400 Subject: [PATCH 10/10] Skip unnecessary render queue object creation This commit skips object creation for the render queue when not needed. Instead of pushing an object onto the queue, and then immediately running the result, you call the function directly. Then, if the render queue is not empty, an object is created and pushed onto the queue. Otherwise, the functionality is just run directly. --- include/display.js | 106 ++++++++++++++++++++++++++++++++++----------- include/rfb.js | 57 +++++------------------- 2 files changed, 91 insertions(+), 72 deletions(-) diff --git a/include/display.js b/include/display.js index 418b431d..c30a977d 100644 --- a/include/display.js +++ b/include/display.js @@ -339,18 +339,41 @@ var Display; this._renderQ = []; }, - fillRect: function (x, y, width, height, color) { - this._setFillColor(color); - this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, 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 - this._viewportLoc.x, y - this._viewportLoc.y, width, height); + } }, - copyImage: function (old_x, old_y, new_x, new_y, w, h) { - var x1 = old_x - this._viewportLoc.x; - var y1 = old_y - this._viewportLoc.y; - var x2 = new_x - this._viewportLoc.x; - var y2 = new_y - this._viewportLoc.y; + 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 { + var x1 = old_x - this._viewportLoc.x; + var y1 = old_y - this._viewportLoc.y; + var x2 = new_x - this._viewportLoc.x; + var y2 = new_y - this._viewportLoc.y; - this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h); + this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h); + } }, // start updating a tile @@ -382,7 +405,7 @@ var Display; data[i + 3] = 255; } } else { - this.fillRect(x, y, width, height, color); + this.fillRect(x, y, width, height, color, true); } }, @@ -413,7 +436,7 @@ var Display; } } } else { - this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color); + this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true); } }, @@ -426,16 +449,34 @@ var Display; // else: No-op -- already done by setSubTile }, - blitImage: function (x, y, width, height, arr, offset) { - if (this._true_color) { + blitImage: function (x, y, width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this.renderQ_push({ + 'type': 'blit', + 'data': arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else if (this._true_color) { this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); } else { this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); } }, - blitRgbImage: function (x, y , width, height, arr, offset) { - if (this._true_color) { + blitRgbImage: function (x, y , width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this.renderQ_push({ + 'type': 'blitRgb', + 'data': arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else if (this._true_color) { this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); } else { // probably wrong? @@ -443,8 +484,24 @@ var Display; } }, - blitRgbxImage: function (x, y, width, height, arr, offset) { - this._rgbxImageData(x, y, this._viewportLoc.x, this._viewportLoc.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 + // 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, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); + } }, blitStringImage: function (str, x, y) { @@ -626,13 +683,12 @@ var Display; _rgbxImageData: function (x, y, vx, vy, width, height, arr, offset) { // NB(directxman12): arr must be an Type Array view - // NB(directxman12): this only works var img; if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) { - img = new ImageData(new Uint8ClampedArray(arr.buffer, 0, width * height * 4), width, height); + 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, 0, width * height * 4)); + img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4)); } this._drawCtx.putImageData(img, x - vx, y - vy); }, @@ -657,19 +713,19 @@ var Display; var a = this._renderQ[0]; switch (a.type) { case 'copy': - this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height); + 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); + 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); + 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); + 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); + this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); break; case 'img': if (a.img.complete) { diff --git a/include/rfb.js b/include/rfb.js index b8615af8..b7a811d5 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -1483,15 +1483,10 @@ var RFB; COPYRECT: function () { this._FBU.bytes = 4; if (this._sock.rQwait("COPYRECT", 4)) { return false; } - this._display.renderQ_push({ - 'type': 'copy', - 'old_x': this._sock.rQshift16(), - 'old_y': this._sock.rQshift16(), - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - }); + 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; @@ -1842,28 +1837,10 @@ var RFB; var rgbx; if (numColors == 2) { rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height); - - /*this._display.renderQ_push({ - 'type': 'blitRgbx', - 'data': rgbx, - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - });*/ - this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0); + 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.renderQ_push({ - 'type': 'blitRgbx', - 'data': rgbx, - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - });*/ - this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0); + this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); } @@ -1905,14 +1882,7 @@ var RFB; data = decompress(this._sock.rQshiftBytes(cl_data)); } - this._display.renderQ_push({ - 'type': 'blitRgb', - 'data': data, - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - }); + this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false); return true; }.bind(this); @@ -1960,16 +1930,9 @@ var RFB; // Determine FBU.bytes switch (cmode) { case "fill": - this._sock.rQskip8(); // shift off ctl - var color = this._sock.rQshiftBytes(this._fb_depth); - this._display.renderQ_push({ - 'type': 'fill', - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height, - 'color': [color[2], color[1], color[0]] - }); + // 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":