You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by vn...@apache.org on 2018/10/01 01:10:52 UTC

[3/7] guacamole-website git commit: Pull guacamole-common-js modules from git for tests. Allow git commit to be overridden.

http://git-wip-us.apache.org/repos/asf/guacamole-website/blob/2de22be5/pub/tests/guac/modules/Keyboard.js
----------------------------------------------------------------------
diff --git a/pub/tests/guac/modules/Keyboard.js b/pub/tests/guac/modules/Keyboard.js
deleted file mode 100644
index ce9f016..0000000
--- a/pub/tests/guac/modules/Keyboard.js
+++ /dev/null
@@ -1,1162 +0,0 @@
-/*
- * Copyright (C) 2013 Glyptodon LLC
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-var Guacamole = Guacamole || {};
-
-/**
- * Provides cross-browser and cross-keyboard keyboard for a specific element.
- * Browser and keyboard layout variation is abstracted away, providing events
- * which represent keys as their corresponding X11 keysym.
- * 
- * @constructor
- * @param {Element} element The Element to use to provide keyboard events.
- */
-Guacamole.Keyboard = function(element) {
-
-    /**
-     * Reference to this Guacamole.Keyboard.
-     * @private
-     */
-    var guac_keyboard = this;
-
-    /**
-     * Fired whenever the user presses a key with the element associated
-     * with this Guacamole.Keyboard in focus.
-     * 
-     * @event
-     * @param {Number} keysym The keysym of the key being pressed.
-     * @return {Boolean} true if the key event should be allowed through to the
-     *                   browser, false otherwise.
-     */
-    this.onkeydown = null;
-
-    /**
-     * Fired whenever the user releases a key with the element associated
-     * with this Guacamole.Keyboard in focus.
-     * 
-     * @event
-     * @param {Number} keysym The keysym of the key being released.
-     */
-    this.onkeyup = null;
-
-    /**
-     * A key event having a corresponding timestamp. This event is non-specific.
-     * Its subclasses should be used instead when recording specific key
-     * events.
-     *
-     * @private
-     * @constructor
-     */
-    var KeyEvent = function() {
-
-        /**
-         * Reference to this key event.
-         */
-        var key_event = this;
-
-        /**
-         * An arbitrary timestamp in milliseconds, indicating this event's
-         * position in time relative to other events.
-         *
-         * @type {Number}
-         */
-        this.timestamp = new Date().getTime();
-
-        /**
-         * Whether the default action of this key event should be prevented.
-         *
-         * @type {Boolean}
-         */
-        this.defaultPrevented = false;
-
-        /**
-         * The keysym of the key associated with this key event, as determined
-         * by a best-effort guess using available event properties and keyboard
-         * state.
-         *
-         * @type {Number}
-         */
-        this.keysym = null;
-
-        /**
-         * Whether the keysym value of this key event is known to be reliable.
-         * If false, the keysym may still be valid, but it's only a best guess,
-         * and future key events may be a better source of information.
-         *
-         * @type {Boolean}
-         */
-        this.reliable = false;
-
-        /**
-         * Returns the number of milliseconds elapsed since this event was
-         * received.
-         *
-         * @return {Number} The number of milliseconds elapsed since this
-         *                  event was received.
-         */
-        this.getAge = function() {
-            return new Date().getTime() - key_event.timestamp;
-        };
-
-    };
-
-    /**
-     * Information related to the pressing of a key, which need not be a key
-     * associated with a printable character. The presence or absence of any
-     * information within this object is browser-dependent.
-     *
-     * @private
-     * @constructor
-     * @augments Guacamole.Keyboard.KeyEvent
-     * @param {Number} keyCode The JavaScript key code of the key pressed.
-     * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key
-     *                               pressed, as defined at:
-     *                               http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
-     * @param {String} key The standard name of the key pressed, as defined at:
-     *                     http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-     * @param {Number} location The location on the keyboard corresponding to
-     *                          the key pressed, as defined at:
-     *                          http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-     */
-    var KeydownEvent = function(keyCode, keyIdentifier, key, location) {
-
-        // We extend KeyEvent
-        KeyEvent.apply(this);
-
-        /**
-         * The JavaScript key code of the key pressed.
-         *
-         * @type {Number}
-         */
-        this.keyCode = keyCode;
-
-        /**
-         * The legacy DOM3 "keyIdentifier" of the key pressed, as defined at:
-         * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
-         *
-         * @type {String}
-         */
-        this.keyIdentifier = keyIdentifier;
-
-        /**
-         * The standard name of the key pressed, as defined at:
-         * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-         * 
-         * @type {String}
-         */
-        this.key = key;
-
-        /**
-         * The location on the keyboard corresponding to the key pressed, as
-         * defined at:
-         * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-         * 
-         * @type {Number}
-         */
-        this.location = location;
-
-        // If key is known from keyCode or DOM3 alone, use that
-        this.keysym =  keysym_from_key_identifier(key, location)
-                    || keysym_from_keycode(keyCode, location);
-
-        // DOM3 and keyCode are reliable sources
-        if (this.keysym)
-            this.reliable = true;
-
-        // Use legacy keyIdentifier as a last resort, if it looks sane
-        if (!this.keysym && key_identifier_sane(keyCode, keyIdentifier))
-            this.keysym = keysym_from_key_identifier(keyIdentifier, location, guac_keyboard.modifiers.shift);
-
-        // Determine whether default action for Alt+combinations must be prevented
-        var prevent_alt =  !guac_keyboard.modifiers.ctrl
-                        && !(navigator && navigator.platform && navigator.platform.match(/^mac/i));
-
-        // Determine whether default action for Ctrl+combinations must be prevented
-        var prevent_ctrl = !guac_keyboard.modifiers.alt;
-
-        // We must rely on the (potentially buggy) keyIdentifier if preventing
-        // the default action is important
-        if ((prevent_ctrl && guac_keyboard.modifiers.ctrl)
-         || (prevent_alt  && guac_keyboard.modifiers.alt)
-         || guac_keyboard.modifiers.meta
-         || guac_keyboard.modifiers.hyper)
-            this.reliable = true;
-
-        // Record most recently known keysym by associated key code
-        recentKeysym[keyCode] = this.keysym;
-
-    };
-
-    KeydownEvent.prototype = new KeyEvent();
-
-    /**
-     * Information related to the pressing of a key, which MUST be
-     * associated with a printable character. The presence or absence of any
-     * information within this object is browser-dependent.
-     *
-     * @private
-     * @constructor
-     * @augments Guacamole.Keyboard.KeyEvent
-     * @param {Number} charCode The Unicode codepoint of the character that
-     *                          would be typed by the key pressed.
-     */
-    var KeypressEvent = function(charCode) {
-
-        // We extend KeyEvent
-        KeyEvent.apply(this);
-
-        /**
-         * The Unicode codepoint of the character that would be typed by the
-         * key pressed.
-         *
-         * @type {Number}
-         */
-        this.charCode = charCode;
-
-        // Pull keysym from char code
-        this.keysym = keysym_from_charcode(charCode);
-
-        // Keypress is always reliable
-        this.reliable = true;
-
-    };
-
-    KeypressEvent.prototype = new KeyEvent();
-
-    /**
-     * Information related to the pressing of a key, which need not be a key
-     * associated with a printable character. The presence or absence of any
-     * information within this object is browser-dependent.
-     *
-     * @private
-     * @constructor
-     * @augments Guacamole.Keyboard.KeyEvent
-     * @param {Number} keyCode The JavaScript key code of the key released.
-     * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key
-     *                               released, as defined at:
-     *                               http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
-     * @param {String} key The standard name of the key released, as defined at:
-     *                     http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-     * @param {Number} location The location on the keyboard corresponding to
-     *                          the key released, as defined at:
-     *                          http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-     */
-    var KeyupEvent = function(keyCode, keyIdentifier, key, location) {
-
-        // We extend KeyEvent
-        KeyEvent.apply(this);
-
-        /**
-         * The JavaScript key code of the key released.
-         *
-         * @type {Number}
-         */
-        this.keyCode = keyCode;
-
-        /**
-         * The legacy DOM3 "keyIdentifier" of the key released, as defined at:
-         * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
-         *
-         * @type {String}
-         */
-        this.keyIdentifier = keyIdentifier;
-
-        /**
-         * The standard name of the key released, as defined at:
-         * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-         * 
-         * @type {String}
-         */
-        this.key = key;
-
-        /**
-         * The location on the keyboard corresponding to the key released, as
-         * defined at:
-         * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-         * 
-         * @type {Number}
-         */
-        this.location = location;
-
-        // If key is known from keyCode or DOM3 alone, use that
-        this.keysym =  recentKeysym[keyCode]
-                    || keysym_from_keycode(keyCode, location)
-                    || keysym_from_key_identifier(key, location); // keyCode is still more reliable for keyup when dead keys are in use
-
-        // Keyup is as reliable as it will ever be
-        this.reliable = true;
-
-    };
-
-    KeyupEvent.prototype = new KeyEvent();
-
-    /**
-     * An array of recorded events, which can be instances of the private
-     * KeydownEvent, KeypressEvent, and KeyupEvent classes.
-     *
-     * @private
-     * @type {KeyEvent[]}
-     */
-    var eventLog = [];
-
-    /**
-     * Map of known JavaScript keycodes which do not map to typable characters
-     * to their X11 keysym equivalents.
-     * @private
-     */
-    var keycodeKeysyms = {
-        8:   [0xFF08], // backspace
-        9:   [0xFF09], // tab
-        12:  [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear       / KP 5
-        13:  [0xFF0D], // enter
-        16:  [0xFFE1, 0xFFE1, 0xFFE2], // shift
-        17:  [0xFFE3, 0xFFE3, 0xFFE4], // ctrl
-        18:  [0xFFE9, 0xFFE9, 0xFE03], // alt
-        19:  [0xFF13], // pause/break
-        20:  [0xFFE5], // caps lock
-        27:  [0xFF1B], // escape
-        32:  [0x0020], // space
-        33:  [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up     / KP 9
-        34:  [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down   / KP 3
-        35:  [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end         / KP 1
-        36:  [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home        / KP 7
-        37:  [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow  / KP 4
-        38:  [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow    / KP 8
-        39:  [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 6
-        40:  [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow  / KP 2
-        45:  [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert      / KP 0
-        46:  [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete      / KP decimal
-        91:  [0xFFEB], // left window key (hyper_l)
-        92:  [0xFF67], // right window key (menu key?)
-        93:  null,     // select key
-        96:  [0xFFB0], // KP 0
-        97:  [0xFFB1], // KP 1
-        98:  [0xFFB2], // KP 2
-        99:  [0xFFB3], // KP 3
-        100: [0xFFB4], // KP 4
-        101: [0xFFB5], // KP 5
-        102: [0xFFB6], // KP 6
-        103: [0xFFB7], // KP 7
-        104: [0xFFB8], // KP 8
-        105: [0xFFB9], // KP 9
-        106: [0xFFAA], // KP multiply
-        107: [0xFFAB], // KP add
-        109: [0xFFAD], // KP subtract
-        110: [0xFFAE], // KP decimal
-        111: [0xFFAF], // KP divide
-        112: [0xFFBE], // f1
-        113: [0xFFBF], // f2
-        114: [0xFFC0], // f3
-        115: [0xFFC1], // f4
-        116: [0xFFC2], // f5
-        117: [0xFFC3], // f6
-        118: [0xFFC4], // f7
-        119: [0xFFC5], // f8
-        120: [0xFFC6], // f9
-        121: [0xFFC7], // f10
-        122: [0xFFC8], // f11
-        123: [0xFFC9], // f12
-        144: [0xFF7F], // num lock
-        145: [0xFF14], // scroll lock
-        225: [0xFE03]  // altgraph (iso_level3_shift)
-    };
-
-    /**
-     * Map of known JavaScript keyidentifiers which do not map to typable
-     * characters to their unshifted X11 keysym equivalents.
-     * @private
-     */
-    var keyidentifier_keysym = {
-        "Again": [0xFF66],
-        "AllCandidates": [0xFF3D],
-        "Alphanumeric": [0xFF30],
-        "Alt": [0xFFE9, 0xFFE9, 0xFE03],
-        "Attn": [0xFD0E],
-        "AltGraph": [0xFE03],
-        "ArrowDown": [0xFF54],
-        "ArrowLeft": [0xFF51],
-        "ArrowRight": [0xFF53],
-        "ArrowUp": [0xFF52],
-        "Backspace": [0xFF08],
-        "CapsLock": [0xFFE5],
-        "Cancel": [0xFF69],
-        "Clear": [0xFF0B],
-        "Convert": [0xFF21],
-        "Copy": [0xFD15],
-        "Crsel": [0xFD1C],
-        "CrSel": [0xFD1C],
-        "CodeInput": [0xFF37],
-        "Compose": [0xFF20],
-        "Control": [0xFFE3, 0xFFE3, 0xFFE4],
-        "ContextMenu": [0xFF67],
-        "DeadGrave": [0xFE50],
-        "DeadAcute": [0xFE51],
-        "DeadCircumflex": [0xFE52],
-        "DeadTilde": [0xFE53],
-        "DeadMacron": [0xFE54],
-        "DeadBreve": [0xFE55],
-        "DeadAboveDot": [0xFE56],
-        "DeadUmlaut": [0xFE57],
-        "DeadAboveRing": [0xFE58],
-        "DeadDoubleacute": [0xFE59],
-        "DeadCaron": [0xFE5A],
-        "DeadCedilla": [0xFE5B],
-        "DeadOgonek": [0xFE5C],
-        "DeadIota": [0xFE5D],
-        "DeadVoicedSound": [0xFE5E],
-        "DeadSemivoicedSound": [0xFE5F],
-        "Delete": [0xFFFF],
-        "Down": [0xFF54],
-        "End": [0xFF57],
-        "Enter": [0xFF0D],
-        "EraseEof": [0xFD06],
-        "Escape": [0xFF1B],
-        "Execute": [0xFF62],
-        "Exsel": [0xFD1D],
-        "ExSel": [0xFD1D],
-        "F1": [0xFFBE],
-        "F2": [0xFFBF],
-        "F3": [0xFFC0],
-        "F4": [0xFFC1],
-        "F5": [0xFFC2],
-        "F6": [0xFFC3],
-        "F7": [0xFFC4],
-        "F8": [0xFFC5],
-        "F9": [0xFFC6],
-        "F10": [0xFFC7],
-        "F11": [0xFFC8],
-        "F12": [0xFFC9],
-        "F13": [0xFFCA],
-        "F14": [0xFFCB],
-        "F15": [0xFFCC],
-        "F16": [0xFFCD],
-        "F17": [0xFFCE],
-        "F18": [0xFFCF],
-        "F19": [0xFFD0],
-        "F20": [0xFFD1],
-        "F21": [0xFFD2],
-        "F22": [0xFFD3],
-        "F23": [0xFFD4],
-        "F24": [0xFFD5],
-        "Find": [0xFF68],
-        "GroupFirst": [0xFE0C],
-        "GroupLast": [0xFE0E],
-        "GroupNext": [0xFE08],
-        "GroupPrevious": [0xFE0A],
-        "FullWidth": null,
-        "HalfWidth": null,
-        "HangulMode": [0xFF31],
-        "Hankaku": [0xFF29],
-        "HanjaMode": [0xFF34],
-        "Help": [0xFF6A],
-        "Hiragana": [0xFF25],
-        "HiraganaKatakana": [0xFF27],
-        "Home": [0xFF50],
-        "Hyper": [0xFFED, 0xFFED, 0xFFEE],
-        "Insert": [0xFF63],
-        "JapaneseHiragana": [0xFF25],
-        "JapaneseKatakana": [0xFF26],
-        "JapaneseRomaji": [0xFF24],
-        "JunjaMode": [0xFF38],
-        "KanaMode": [0xFF2D],
-        "KanjiMode": [0xFF21],
-        "Katakana": [0xFF26],
-        "Left": [0xFF51],
-        "Meta": [0xFFE7, 0xFFE7, 0xFFE8],
-        "ModeChange": [0xFF7E],
-        "NumLock": [0xFF7F],
-        "PageDown": [0xFF56],
-        "PageUp": [0xFF55],
-        "Pause": [0xFF13],
-        "Play": [0xFD16],
-        "PreviousCandidate": [0xFF3E],
-        "PrintScreen": [0xFD1D],
-        "Redo": [0xFF66],
-        "Right": [0xFF53],
-        "RomanCharacters": null,
-        "Scroll": [0xFF14],
-        "Select": [0xFF60],
-        "Separator": [0xFFAC],
-        "Shift": [0xFFE1, 0xFFE1, 0xFFE2],
-        "SingleCandidate": [0xFF3C],
-        "Super": [0xFFEB, 0xFFEB, 0xFFEC],
-        "Tab": [0xFF09],
-        "Up": [0xFF52],
-        "Undo": [0xFF65],
-        "Win": [0xFFEB],
-        "Zenkaku": [0xFF28],
-        "ZenkakuHankaku": [0xFF2A]
-    };
-
-    /**
-     * All keysyms which should not repeat when held down.
-     * @private
-     */
-    var no_repeat = {
-        0xFE03: true, // ISO Level 3 Shift (AltGr)
-        0xFFE1: true, // Left shift
-        0xFFE2: true, // Right shift
-        0xFFE3: true, // Left ctrl 
-        0xFFE4: true, // Right ctrl 
-        0xFFE7: true, // Left meta 
-        0xFFE8: true, // Right meta 
-        0xFFE9: true, // Left alt
-        0xFFEA: true, // Right alt
-        0xFFEB: true, // Left hyper
-        0xFFEC: true  // Right hyper
-    };
-
-    /**
-     * All modifiers and their states.
-     */
-    this.modifiers = new Guacamole.Keyboard.ModifierState();
-        
-    /**
-     * The state of every key, indexed by keysym. If a particular key is
-     * pressed, the value of pressed for that keysym will be true. If a key
-     * is not currently pressed, it will not be defined. 
-     */
-    this.pressed = {};
-
-    /**
-     * The last result of calling the onkeydown handler for each key, indexed
-     * by keysym. This is used to prevent/allow default actions for key events,
-     * even when the onkeydown handler cannot be called again because the key
-     * is (theoretically) still pressed.
-     *
-     * @private
-     */
-    var last_keydown_result = {};
-
-    /**
-     * The keysym most recently associated with a given keycode when keydown
-     * fired. This object maps keycodes to keysyms.
-     *
-     * @private
-     * @type {Object.<Number, Number>}
-     */
-    var recentKeysym = {};
-
-    /**
-     * Timeout before key repeat starts.
-     * @private
-     */
-    var key_repeat_timeout = null;
-
-    /**
-     * Interval which presses and releases the last key pressed while that
-     * key is still being held down.
-     * @private
-     */
-    var key_repeat_interval = null;
-
-    /**
-     * Given an array of keysyms indexed by location, returns the keysym
-     * for the given location, or the keysym for the standard location if
-     * undefined.
-     * 
-     * @private
-     * @param {Number[]} keysyms
-     *     An array of keysyms, where the index of the keysym in the array is
-     *     the location value.
-     *
-     * @param {Number} location
-     *     The location on the keyboard corresponding to the key pressed, as
-     *     defined at: http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-     */
-    var get_keysym = function get_keysym(keysyms, location) {
-
-        if (!keysyms)
-            return null;
-
-        return keysyms[location] || keysyms[0];
-    };
-
-    function keysym_from_key_identifier(identifier, location, shifted) {
-
-        if (!identifier)
-            return null;
-
-        var typedCharacter;
-
-        // If identifier is U+xxxx, decode Unicode character 
-        var unicodePrefixLocation = identifier.indexOf("U+");
-        if (unicodePrefixLocation >= 0) {
-            var hex = identifier.substring(unicodePrefixLocation+2);
-            typedCharacter = String.fromCharCode(parseInt(hex, 16));
-        }
-
-        // If single character and not keypad, use that as typed character
-        else if (identifier.length === 1 && location !== 3)
-            typedCharacter = identifier;
-
-        // Otherwise, look up corresponding keysym
-        else
-            return get_keysym(keyidentifier_keysym[identifier], location);
-
-        // Alter case if necessary
-        if (shifted === true)
-            typedCharacter = typedCharacter.toUpperCase();
-        else if (shifted === false)
-            typedCharacter = typedCharacter.toLowerCase();
-
-        // Get codepoint
-        var codepoint = typedCharacter.charCodeAt(0);
-        return keysym_from_charcode(codepoint);
-
-    }
-
-    function isControlCharacter(codepoint) {
-        return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F);
-    }
-
-    function keysym_from_charcode(codepoint) {
-
-        // Keysyms for control characters
-        if (isControlCharacter(codepoint)) return 0xFF00 | codepoint;
-
-        // Keysyms for ASCII chars
-        if (codepoint >= 0x0000 && codepoint <= 0x00FF)
-            return codepoint;
-
-        // Keysyms for Unicode
-        if (codepoint >= 0x0100 && codepoint <= 0x10FFFF)
-            return 0x01000000 | codepoint;
-
-        return null;
-
-    }
-
-    function keysym_from_keycode(keyCode, location) {
-        return get_keysym(keycodeKeysyms[keyCode], location);
-    }
-
-    /**
-     * Heuristically detects if the legacy keyIdentifier property of
-     * a keydown/keyup event looks incorrectly derived. Chrome, and
-     * presumably others, will produce the keyIdentifier by assuming
-     * the keyCode is the Unicode codepoint for that key. This is not
-     * correct in all cases.
-     *
-     * @private
-     * @param {Number} keyCode
-     *     The keyCode from a browser keydown/keyup event.
-     *
-     * @param {String} keyIdentifier
-     *     The legacy keyIdentifier from a browser keydown/keyup event.
-     *
-     * @returns {Boolean}
-     *     true if the keyIdentifier looks sane, false if the keyIdentifier
-     *     appears incorrectly derived or is missing entirely.
-     */
-    var key_identifier_sane = function key_identifier_sane(keyCode, keyIdentifier) {
-
-        // Missing identifier is not sane
-        if (!keyIdentifier)
-            return false;
-
-        // Assume non-Unicode keyIdentifier values are sane
-        var unicodePrefixLocation = keyIdentifier.indexOf("U+");
-        if (unicodePrefixLocation === -1)
-            return true;
-
-        // If the Unicode codepoint isn't identical to the keyCode,
-        // then the identifier is likely correct
-        var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16);
-        if (keyCode !== codepoint)
-            return true;
-
-        // The keyCodes for A-Z and 0-9 are actually identical to their
-        // Unicode codepoints
-        if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57))
-            return true;
-
-        // The keyIdentifier does NOT appear sane
-        return false;
-
-    };
-
-    /**
-     * Marks a key as pressed, firing the keydown event if registered. Key
-     * repeat for the pressed key will start after a delay if that key is
-     * not a modifier. The return value of this function depends on the
-     * return value of the keydown event handler, if any.
-     * 
-     * @param {Number} keysym The keysym of the key to press.
-     * @return {Boolean} true if event should NOT be canceled, false otherwise.
-     */
-    this.press = function(keysym) {
-
-        // Don't bother with pressing the key if the key is unknown
-        if (keysym === null) return;
-
-        // Only press if released
-        if (!guac_keyboard.pressed[keysym]) {
-
-            // Mark key as pressed
-            guac_keyboard.pressed[keysym] = true;
-
-            // Send key event
-            if (guac_keyboard.onkeydown) {
-                var result = guac_keyboard.onkeydown(keysym);
-                last_keydown_result[keysym] = result;
-
-                // Stop any current repeat
-                window.clearTimeout(key_repeat_timeout);
-                window.clearInterval(key_repeat_interval);
-
-                // Repeat after a delay as long as pressed
-                if (!no_repeat[keysym])
-                    key_repeat_timeout = window.setTimeout(function() {
-                        key_repeat_interval = window.setInterval(function() {
-                            guac_keyboard.onkeyup(keysym);
-                            guac_keyboard.onkeydown(keysym);
-                        }, 50);
-                    }, 500);
-
-                return result;
-            }
-        }
-
-        // Return the last keydown result by default, resort to false if unknown
-        return last_keydown_result[keysym] || false;
-
-    };
-
-    /**
-     * Marks a key as released, firing the keyup event if registered.
-     * 
-     * @param {Number} keysym The keysym of the key to release.
-     */
-    this.release = function(keysym) {
-
-        // Only release if pressed
-        if (guac_keyboard.pressed[keysym]) {
-            
-            // Mark key as released
-            delete guac_keyboard.pressed[keysym];
-
-            // Stop repeat
-            window.clearTimeout(key_repeat_timeout);
-            window.clearInterval(key_repeat_interval);
-
-            // Send key event
-            if (keysym !== null && guac_keyboard.onkeyup)
-                guac_keyboard.onkeyup(keysym);
-
-        }
-
-    };
-
-    /**
-     * Resets the state of this keyboard, releasing all keys, and firing keyup
-     * events for each released key.
-     */
-    this.reset = function() {
-
-        // Release all pressed keys
-        for (var keysym in guac_keyboard.pressed)
-            guac_keyboard.release(parseInt(keysym));
-
-        // Clear event log
-        eventLog = [];
-
-    };
-
-    /**
-     * Given a keyboard event, updates the local modifier state and remote
-     * key state based on the modifier flags within the event. This function
-     * pays no attention to keycodes.
-     *
-     * @private
-     * @param {KeyboardEvent} e
-     *     The keyboard event containing the flags to update.
-     */
-    var update_modifier_state = function update_modifier_state(e) {
-
-        // Get state
-        var state = Guacamole.Keyboard.ModifierState.fromKeyboardEvent(e);
-
-        // Release alt if implicitly released
-        if (guac_keyboard.modifiers.alt && state.alt === false) {
-            guac_keyboard.release(0xFFE9); // Left alt
-            guac_keyboard.release(0xFFEA); // Right alt
-            guac_keyboard.release(0xFE03); // AltGr
-        }
-
-        // Release shift if implicitly released
-        if (guac_keyboard.modifiers.shift && state.shift === false) {
-            guac_keyboard.release(0xFFE1); // Left shift
-            guac_keyboard.release(0xFFE2); // Right shift
-        }
-
-        // Release ctrl if implicitly released
-        if (guac_keyboard.modifiers.ctrl && state.ctrl === false) {
-            guac_keyboard.release(0xFFE3); // Left ctrl 
-            guac_keyboard.release(0xFFE4); // Right ctrl 
-        }
-
-        // Release meta if implicitly released
-        if (guac_keyboard.modifiers.meta && state.meta === false) {
-            guac_keyboard.release(0xFFE7); // Left meta 
-            guac_keyboard.release(0xFFE8); // Right meta 
-        }
-
-        // Release hyper if implicitly released
-        if (guac_keyboard.modifiers.hyper && state.hyper === false) {
-            guac_keyboard.release(0xFFEB); // Left hyper
-            guac_keyboard.release(0xFFEC); // Right hyper
-        }
-
-        // Update state
-        guac_keyboard.modifiers = state;
-
-    };
-
-    /**
-     * Reads through the event log, removing events from the head of the log
-     * when the corresponding true key presses are known (or as known as they
-     * can be).
-     * 
-     * @private
-     * @return {Boolean} Whether the default action of the latest event should
-     *                   be prevented.
-     */
-    function interpret_events() {
-
-        // Do not prevent default if no event could be interpreted
-        var handled_event = interpret_event();
-        if (!handled_event)
-            return false;
-
-        // Interpret as much as possible
-        var last_event;
-        do {
-            last_event = handled_event;
-            handled_event = interpret_event();
-        } while (handled_event !== null);
-
-        return last_event.defaultPrevented;
-
-    }
-
-    /**
-     * Releases Ctrl+Alt, if both are currently pressed and the given keysym
-     * looks like a key that may require AltGr.
-     *
-     * @private
-     * @param {Number} keysym The key that was just pressed.
-     */
-    var release_simulated_altgr = function release_simulated_altgr(keysym) {
-
-        // Both Ctrl+Alt must be pressed if simulated AltGr is in use
-        if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt)
-            return;
-
-        // Assume [A-Z] never require AltGr
-        if (keysym >= 0x0041 && keysym <= 0x005A)
-            return;
-
-        // Assume [a-z] never require AltGr
-        if (keysym >= 0x0061 && keysym <= 0x007A)
-            return;
-
-        // Release Ctrl+Alt if the keysym is printable
-        if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) {
-            guac_keyboard.release(0xFFE3); // Left ctrl 
-            guac_keyboard.release(0xFFE4); // Right ctrl 
-            guac_keyboard.release(0xFFE9); // Left alt
-            guac_keyboard.release(0xFFEA); // Right alt
-        }
-
-    };
-
-    /**
-     * Reads through the event log, interpreting the first event, if possible,
-     * and returning that event. If no events can be interpreted, due to a
-     * total lack of events or the need for more events, null is returned. Any
-     * interpreted events are automatically removed from the log.
-     * 
-     * @private
-     * @return {KeyEvent}
-     *     The first key event in the log, if it can be interpreted, or null
-     *     otherwise.
-     */
-    var interpret_event = function interpret_event() {
-
-        // Peek at first event in log
-        var first = eventLog[0];
-        if (!first)
-            return null;
-
-        // Keydown event
-        if (first instanceof KeydownEvent) {
-
-            var keysym = null;
-            var accepted_events = [];
-
-            // If event itself is reliable, no need to wait for other events
-            if (first.reliable) {
-                keysym = first.keysym;
-                accepted_events = eventLog.splice(0, 1);
-            }
-
-            // If keydown is immediately followed by a keypress, use the indicated character
-            else if (eventLog[1] instanceof KeypressEvent) {
-                keysym = eventLog[1].keysym;
-                accepted_events = eventLog.splice(0, 2);
-            }
-
-            // If keydown is immediately followed by anything else, then no
-            // keypress can possibly occur to clarify this event, and we must
-            // handle it now
-            else if (eventLog[1]) {
-                keysym = first.keysym;
-                accepted_events = eventLog.splice(0, 1);
-            }
-
-            // Fire a key press if valid events were found
-            if (accepted_events.length > 0) {
-
-                if (keysym) {
-
-                    // Fire event
-                    release_simulated_altgr(keysym);
-                    var defaultPrevented = !guac_keyboard.press(keysym);
-                    recentKeysym[first.keyCode] = keysym;
-
-                    // If a key is pressed while meta is held down, the keyup will
-                    // never be sent in Chrome, so send it now. (bug #108404)
-                    if (guac_keyboard.modifiers.meta && keysym !== 0xFFE7 && keysym !== 0xFFE8)
-                        guac_keyboard.release(keysym);
-
-                    // Record whether default was prevented
-                    for (var i=0; i<accepted_events.length; i++)
-                        accepted_events[i].defaultPrevented = defaultPrevented;
-
-                }
-
-                return first;
-
-            }
-
-        } // end if keydown
-
-        // Keyup event
-        else if (first instanceof KeyupEvent) {
-
-            // Release specific key if known
-            var keysym = first.keysym;
-            if (keysym) {
-                guac_keyboard.release(keysym);
-                first.defaultPrevented = true;
-            }
-
-            // Otherwise, fall back to releasing all keys
-            else {
-                guac_keyboard.reset();
-                return first;
-            }
-
-            return eventLog.shift();
-
-        } // end if keyup
-
-        // Ignore any other type of event (keypress by itself is invalid)
-        else
-            return eventLog.shift();
-
-        // No event interpreted
-        return null;
-
-    };
-
-    /**
-     * Returns the keyboard location of the key associated with the given
-     * keyboard event. The location differentiates key events which otherwise
-     * have the same keycode, such as left shift vs. right shift.
-     *
-     * @private
-     * @param {KeyboardEvent} e
-     *     A JavaScript keyboard event, as received through the DOM via a
-     *     "keydown", "keyup", or "keypress" handler.
-     *
-     * @returns {Number}
-     *     The location of the key event on the keyboard, as defined at:
-     *     http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
-     */
-    var getEventLocation = function getEventLocation(e) {
-
-        // Use standard location, if possible
-        if ('location' in e)
-            return e.location;
-
-        // Failing that, attempt to use deprecated keyLocation
-        if ('keyLocation' in e)
-            return e.keyLocation;
-
-        // If no location is available, assume left side
-        return 0;
-
-    };
-
-    // When key pressed
-    element.addEventListener("keydown", function(e) {
-
-        // Only intercept if handler set
-        if (!guac_keyboard.onkeydown) return;
-
-        var keyCode;
-        if (window.event) keyCode = window.event.keyCode;
-        else if (e.which) keyCode = e.which;
-
-        // Fix modifier states
-        update_modifier_state(e);
-
-        // Ignore (but do not prevent) the "composition" keycode sent by some
-        // browsers when an IME is in use (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html)
-        if (keyCode === 229)
-            return;
-
-        // Log event
-        var keydownEvent = new KeydownEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e));
-        eventLog.push(keydownEvent);
-
-        // Interpret as many events as possible, prevent default if indicated
-        if (interpret_events())
-            e.preventDefault();
-
-    }, true);
-
-    // When key pressed
-    element.addEventListener("keypress", function(e) {
-
-        // Only intercept if handler set
-        if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
-
-        var charCode;
-        if (window.event) charCode = window.event.keyCode;
-        else if (e.which) charCode = e.which;
-
-        // Fix modifier states
-        update_modifier_state(e);
-
-        // Log event
-        var keypressEvent = new KeypressEvent(charCode);
-        eventLog.push(keypressEvent);
-
-        // Interpret as many events as possible, prevent default if indicated
-        if (interpret_events())
-            e.preventDefault();
-
-    }, true);
-
-    // When key released
-    element.addEventListener("keyup", function(e) {
-
-        // Only intercept if handler set
-        if (!guac_keyboard.onkeyup) return;
-
-        e.preventDefault();
-
-        var keyCode;
-        if (window.event) keyCode = window.event.keyCode;
-        else if (e.which) keyCode = e.which;
-        
-        // Fix modifier states
-        update_modifier_state(e);
-
-        // Log event, call for interpretation
-        var keyupEvent = new KeyupEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e));
-        eventLog.push(keyupEvent);
-        interpret_events();
-
-    }, true);
-
-};
-
-/**
- * The state of all supported keyboard modifiers.
- * @constructor
- */
-Guacamole.Keyboard.ModifierState = function() {
-    
-    /**
-     * Whether shift is currently pressed.
-     * @type {Boolean}
-     */
-    this.shift = false;
-    
-    /**
-     * Whether ctrl is currently pressed.
-     * @type {Boolean}
-     */
-    this.ctrl = false;
-    
-    /**
-     * Whether alt is currently pressed.
-     * @type {Boolean}
-     */
-    this.alt = false;
-    
-    /**
-     * Whether meta (apple key) is currently pressed.
-     * @type {Boolean}
-     */
-    this.meta = false;
-
-    /**
-     * Whether hyper (windows key) is currently pressed.
-     * @type {Boolean}
-     */
-    this.hyper = false;
-    
-};
-
-/**
- * Returns the modifier state applicable to the keyboard event given.
- * 
- * @param {KeyboardEvent} e The keyboard event to read.
- * @returns {Guacamole.Keyboard.ModifierState} The current state of keyboard
- *                                             modifiers.
- */
-Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) {
-    
-    var state = new Guacamole.Keyboard.ModifierState();
-
-    // Assign states from old flags
-    state.shift = e.shiftKey;
-    state.ctrl  = e.ctrlKey;
-    state.alt   = e.altKey;
-    state.meta  = e.metaKey;
-
-    // Use DOM3 getModifierState() for others
-    if (e.getModifierState) {
-        state.hyper = e.getModifierState("OS")
-                   || e.getModifierState("Super")
-                   || e.getModifierState("Hyper")
-                   || e.getModifierState("Win");
-    }
-
-    return state;
-    
-};

http://git-wip-us.apache.org/repos/asf/guacamole-website/blob/2de22be5/pub/tests/guac/modules/Layer.js
----------------------------------------------------------------------
diff --git a/pub/tests/guac/modules/Layer.js b/pub/tests/guac/modules/Layer.js
deleted file mode 100644
index d9cf7f2..0000000
--- a/pub/tests/guac/modules/Layer.js
+++ /dev/null
@@ -1,904 +0,0 @@
-/*
- * Copyright (C) 2013 Glyptodon LLC
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-var Guacamole = Guacamole || {};
-
-/**
- * Abstract ordered drawing surface. Each Layer contains a canvas element and
- * provides simple drawing instructions for drawing to that canvas element,
- * however unlike the canvas element itself, drawing operations on a Layer are
- * guaranteed to run in order, even if such an operation must wait for an image
- * to load before completing.
- * 
- * @constructor
- * 
- * @param {Number} width The width of the Layer, in pixels. The canvas element
- *                       backing this Layer will be given this width.
- *                       
- * @param {Number} height The height of the Layer, in pixels. The canvas element
- *                        backing this Layer will be given this height.
- */
-Guacamole.Layer = function(width, height) {
-
-    /**
-     * Reference to this Layer.
-     * @private
-     */
-    var layer = this;
-
-    /**
-     * The canvas element backing this Layer.
-     * @private
-     */
-    var canvas = document.createElement("canvas");
-
-    /**
-     * The 2D display context of the canvas element backing this Layer.
-     * @private
-     */
-    var context = canvas.getContext("2d");
-    context.save();
-
-    /**
-     * Whether a new path should be started with the next path drawing
-     * operations.
-     * @private
-     */
-    var pathClosed = true;
-
-    /**
-     * The number of states on the state stack.
-     * 
-     * Note that there will ALWAYS be one element on the stack, but that
-     * element is not exposed. It is only used to reset the layer to its
-     * initial state.
-     * 
-     * @private
-     */
-    var stackSize = 0;
-
-    /**
-     * Map of all Guacamole channel masks to HTML5 canvas composite operation
-     * names. Not all channel mask combinations are currently implemented.
-     * @private
-     */
-    var compositeOperation = {
-     /* 0x0 NOT IMPLEMENTED */
-        0x1: "destination-in",
-        0x2: "destination-out",
-     /* 0x3 NOT IMPLEMENTED */
-        0x4: "source-in",
-     /* 0x5 NOT IMPLEMENTED */
-        0x6: "source-atop",
-     /* 0x7 NOT IMPLEMENTED */
-        0x8: "source-out",
-        0x9: "destination-atop",
-        0xA: "xor",
-        0xB: "destination-over",
-        0xC: "copy",
-     /* 0xD NOT IMPLEMENTED */
-        0xE: "source-over",
-        0xF: "lighter"
-    };
-
-    /**
-     * Resizes the canvas element backing this Layer without testing the
-     * new size. This function should only be used internally.
-     * 
-     * @private
-     * @param {Number} newWidth The new width to assign to this Layer.
-     * @param {Number} newHeight The new height to assign to this Layer.
-     */
-    function resize(newWidth, newHeight) {
-
-        // Only preserve old data if width/height are both non-zero
-        var oldData = null;
-        if (layer.width !== 0 && layer.height !== 0) {
-
-            // Create canvas and context for holding old data
-            oldData = document.createElement("canvas");
-            oldData.width = layer.width;
-            oldData.height = layer.height;
-
-            var oldDataContext = oldData.getContext("2d");
-
-            // Copy image data from current
-            oldDataContext.drawImage(canvas,
-                    0, 0, layer.width, layer.height,
-                    0, 0, layer.width, layer.height);
-
-        }
-
-        // Preserve composite operation
-        var oldCompositeOperation = context.globalCompositeOperation;
-
-        // Resize canvas
-        canvas.width = newWidth;
-        canvas.height = newHeight;
-
-        // Redraw old data, if any
-        if (oldData)
-                context.drawImage(oldData, 
-                    0, 0, layer.width, layer.height,
-                    0, 0, layer.width, layer.height);
-
-        // Restore composite operation
-        context.globalCompositeOperation = oldCompositeOperation;
-
-        layer.width = newWidth;
-        layer.height = newHeight;
-
-        // Acknowledge reset of stack (happens on resize of canvas)
-        stackSize = 0;
-        context.save();
-
-    }
-
-    /**
-     * Given the X and Y coordinates of the upper-left corner of a rectangle
-     * and the rectangle's width and height, resize the backing canvas element
-     * as necessary to ensure that the rectangle fits within the canvas
-     * element's coordinate space. This function will only make the canvas
-     * larger. If the rectangle already fits within the canvas element's
-     * coordinate space, the canvas is left unchanged.
-     * 
-     * @private
-     * @param {Number} x The X coordinate of the upper-left corner of the
-     *                   rectangle to fit.
-     * @param {Number} y The Y coordinate of the upper-left corner of the
-     *                   rectangle to fit.
-     * @param {Number} w The width of the the rectangle to fit.
-     * @param {Number} h The height of the the rectangle to fit.
-     */
-    function fitRect(x, y, w, h) {
-        
-        // Calculate bounds
-        var opBoundX = w + x;
-        var opBoundY = h + y;
-        
-        // Determine max width
-        var resizeWidth;
-        if (opBoundX > layer.width)
-            resizeWidth = opBoundX;
-        else
-            resizeWidth = layer.width;
-
-        // Determine max height
-        var resizeHeight;
-        if (opBoundY > layer.height)
-            resizeHeight = opBoundY;
-        else
-            resizeHeight = layer.height;
-
-        // Resize if necessary
-        layer.resize(resizeWidth, resizeHeight);
-
-    }
-
-    /**
-     * Set to true if this Layer should resize itself to accomodate the
-     * dimensions of any drawing operation, and false (the default) otherwise.
-     * 
-     * Note that setting this property takes effect immediately, and thus may
-     * take effect on operations that were started in the past but have not
-     * yet completed. If you wish the setting of this flag to only modify
-     * future operations, you will need to make the setting of this flag an
-     * operation with sync().
-     * 
-     * @example
-     * // Set autosize to true for all future operations
-     * layer.sync(function() {
-     *     layer.autosize = true;
-     * });
-     * 
-     * @type {Boolean}
-     * @default false
-     */
-    this.autosize = false;
-
-    /**
-     * The current width of this layer.
-     * @type {Number}
-     */
-    this.width = width;
-
-    /**
-     * The current height of this layer.
-     * @type {Number}
-     */
-    this.height = height;
-
-    /**
-     * Returns the canvas element backing this Layer.
-     * @returns {Element} The canvas element backing this Layer.
-     */
-    this.getCanvas = function() {
-        return canvas;
-    };
-
-    /**
-     * Changes the size of this Layer to the given width and height. Resizing
-     * is only attempted if the new size provided is actually different from
-     * the current size.
-     * 
-     * @param {Number} newWidth The new width to assign to this Layer.
-     * @param {Number} newHeight The new height to assign to this Layer.
-     */
-    this.resize = function(newWidth, newHeight) {
-        if (newWidth !== layer.width || newHeight !== layer.height)
-            resize(newWidth, newHeight);
-    };
-
-    /**
-     * Draws the specified image at the given coordinates. The image specified
-     * must already be loaded.
-     * 
-     * @param {Number} x The destination X coordinate.
-     * @param {Number} y The destination Y coordinate.
-     * @param {Image} image The image to draw. Note that this is an Image
-     *                      object - not a URL.
-     */
-    this.drawImage = function(x, y, image) {
-        if (layer.autosize) fitRect(x, y, image.width, image.height);
-        context.drawImage(image, x, y);
-    };
-
-    /**
-     * Transfer a rectangle of image data from one Layer to this Layer using the
-     * specified transfer function.
-     * 
-     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
-     * @param {Number} srcx The X coordinate of the upper-left corner of the
-     *                      rectangle within the source Layer's coordinate
-     *                      space to copy data from.
-     * @param {Number} srcy The Y coordinate of the upper-left corner of the
-     *                      rectangle within the source Layer's coordinate
-     *                      space to copy data from.
-     * @param {Number} srcw The width of the rectangle within the source Layer's
-     *                      coordinate space to copy data from.
-     * @param {Number} srch The height of the rectangle within the source
-     *                      Layer's coordinate space to copy data from.
-     * @param {Number} x The destination X coordinate.
-     * @param {Number} y The destination Y coordinate.
-     * @param {Function} transferFunction The transfer function to use to
-     *                                    transfer data from source to
-     *                                    destination.
-     */
-    this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
-
-        var srcCanvas = srcLayer.getCanvas();
-
-        // If entire rectangle outside source canvas, stop
-        if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
-
-        // Otherwise, clip rectangle to area
-        if (srcx + srcw > srcCanvas.width)
-            srcw = srcCanvas.width - srcx;
-
-        if (srcy + srch > srcCanvas.height)
-            srch = srcCanvas.height - srcy;
-
-        // Stop if nothing to draw.
-        if (srcw === 0 || srch === 0) return;
-
-        if (layer.autosize) fitRect(x, y, srcw, srch);
-
-        // Get image data from src and dst
-        var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
-        var dst = context.getImageData(x , y, srcw, srch);
-
-        // Apply transfer for each pixel
-        for (var i=0; i<srcw*srch*4; i+=4) {
-
-            // Get source pixel environment
-            var src_pixel = new Guacamole.Layer.Pixel(
-                src.data[i],
-                src.data[i+1],
-                src.data[i+2],
-                src.data[i+3]
-            );
-                
-            // Get destination pixel environment
-            var dst_pixel = new Guacamole.Layer.Pixel(
-                dst.data[i],
-                dst.data[i+1],
-                dst.data[i+2],
-                dst.data[i+3]
-            );
-
-            // Apply transfer function
-            transferFunction(src_pixel, dst_pixel);
-
-            // Save pixel data
-            dst.data[i  ] = dst_pixel.red;
-            dst.data[i+1] = dst_pixel.green;
-            dst.data[i+2] = dst_pixel.blue;
-            dst.data[i+3] = dst_pixel.alpha;
-
-        }
-
-        // Draw image data
-        context.putImageData(dst, x, y);
-
-    };
-
-    /**
-     * Put a rectangle of image data from one Layer to this Layer directly
-     * without performing any alpha blending. Simply copy the data.
-     * 
-     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
-     * @param {Number} srcx The X coordinate of the upper-left corner of the
-     *                      rectangle within the source Layer's coordinate
-     *                      space to copy data from.
-     * @param {Number} srcy The Y coordinate of the upper-left corner of the
-     *                      rectangle within the source Layer's coordinate
-     *                      space to copy data from.
-     * @param {Number} srcw The width of the rectangle within the source Layer's
-     *                      coordinate space to copy data from.
-     * @param {Number} srch The height of the rectangle within the source
-     *                      Layer's coordinate space to copy data from.
-     * @param {Number} x The destination X coordinate.
-     * @param {Number} y The destination Y coordinate.
-     */
-    this.put = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
-
-        var srcCanvas = srcLayer.getCanvas();
-
-        // If entire rectangle outside source canvas, stop
-        if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
-
-        // Otherwise, clip rectangle to area
-        if (srcx + srcw > srcCanvas.width)
-            srcw = srcCanvas.width - srcx;
-
-        if (srcy + srch > srcCanvas.height)
-            srch = srcCanvas.height - srcy;
-
-        // Stop if nothing to draw.
-        if (srcw === 0 || srch === 0) return;
-
-        if (layer.autosize) fitRect(x, y, srcw, srch);
-
-        // Get image data from src and dst
-        var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
-        context.putImageData(src, x, y);
-
-    };
-
-    /**
-     * Copy a rectangle of image data from one Layer to this Layer. This
-     * operation will copy exactly the image data that will be drawn once all
-     * operations of the source Layer that were pending at the time this
-     * function was called are complete. This operation will not alter the
-     * size of the source Layer even if its autosize property is set to true.
-     * 
-     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
-     * @param {Number} srcx The X coordinate of the upper-left corner of the
-     *                      rectangle within the source Layer's coordinate
-     *                      space to copy data from.
-     * @param {Number} srcy The Y coordinate of the upper-left corner of the
-     *                      rectangle within the source Layer's coordinate
-     *                      space to copy data from.
-     * @param {Number} srcw The width of the rectangle within the source Layer's
-     *                      coordinate space to copy data from.
-     * @param {Number} srch The height of the rectangle within the source
-     *                      Layer's coordinate space to copy data from.
-     * @param {Number} x The destination X coordinate.
-     * @param {Number} y The destination Y coordinate.
-     */
-    this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
-
-        var srcCanvas = srcLayer.getCanvas();
-
-        // If entire rectangle outside source canvas, stop
-        if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
-
-        // Otherwise, clip rectangle to area
-        if (srcx + srcw > srcCanvas.width)
-            srcw = srcCanvas.width - srcx;
-
-        if (srcy + srch > srcCanvas.height)
-            srch = srcCanvas.height - srcy;
-
-        // Stop if nothing to draw.
-        if (srcw === 0 || srch === 0) return;
-
-        if (layer.autosize) fitRect(x, y, srcw, srch);
-        context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
-
-    };
-
-    /**
-     * Starts a new path at the specified point.
-     * 
-     * @param {Number} x The X coordinate of the point to draw.
-     * @param {Number} y The Y coordinate of the point to draw.
-     */
-    this.moveTo = function(x, y) {
-        
-        // Start a new path if current path is closed
-        if (pathClosed) {
-            context.beginPath();
-            pathClosed = false;
-        }
-        
-        if (layer.autosize) fitRect(x, y, 0, 0);
-        context.moveTo(x, y);
-
-    };
-
-    /**
-     * Add the specified line to the current path.
-     * 
-     * @param {Number} x The X coordinate of the endpoint of the line to draw.
-     * @param {Number} y The Y coordinate of the endpoint of the line to draw.
-     */
-    this.lineTo = function(x, y) {
-        
-        // Start a new path if current path is closed
-        if (pathClosed) {
-            context.beginPath();
-            pathClosed = false;
-        }
-        
-        if (layer.autosize) fitRect(x, y, 0, 0);
-        context.lineTo(x, y);
-        
-    };
-
-    /**
-     * Add the specified arc to the current path.
-     * 
-     * @param {Number} x The X coordinate of the center of the circle which
-     *                   will contain the arc.
-     * @param {Number} y The Y coordinate of the center of the circle which
-     *                   will contain the arc.
-     * @param {Number} radius The radius of the circle.
-     * @param {Number} startAngle The starting angle of the arc, in radians.
-     * @param {Number} endAngle The ending angle of the arc, in radians.
-     * @param {Boolean} negative Whether the arc should be drawn in order of
-     *                           decreasing angle.
-     */
-    this.arc = function(x, y, radius, startAngle, endAngle, negative) {
-        
-        // Start a new path if current path is closed
-        if (pathClosed) {
-            context.beginPath();
-            pathClosed = false;
-        }
-        
-        if (layer.autosize) fitRect(x, y, 0, 0);
-        context.arc(x, y, radius, startAngle, endAngle, negative);
-        
-    };
-
-    /**
-     * Starts a new path at the specified point.
-     * 
-     * @param {Number} cp1x The X coordinate of the first control point.
-     * @param {Number} cp1y The Y coordinate of the first control point.
-     * @param {Number} cp2x The X coordinate of the second control point.
-     * @param {Number} cp2y The Y coordinate of the second control point.
-     * @param {Number} x The X coordinate of the endpoint of the curve.
-     * @param {Number} y The Y coordinate of the endpoint of the curve.
-     */
-    this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
-        
-        // Start a new path if current path is closed
-        if (pathClosed) {
-            context.beginPath();
-            pathClosed = false;
-        }
-        
-        if (layer.autosize) fitRect(x, y, 0, 0);
-        context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
-        
-    };
-
-    /**
-     * Closes the current path by connecting the end point with the start
-     * point (if any) with a straight line.
-     */
-    this.close = function() {
-        context.closePath();
-        pathClosed = true;
-    };
-
-    /**
-     * Add the specified rectangle to the current path.
-     * 
-     * @param {Number} x The X coordinate of the upper-left corner of the
-     *                   rectangle to draw.
-     * @param {Number} y The Y coordinate of the upper-left corner of the
-     *                   rectangle to draw.
-     * @param {Number} w The width of the rectangle to draw.
-     * @param {Number} h The height of the rectangle to draw.
-     */
-    this.rect = function(x, y, w, h) {
-            
-        // Start a new path if current path is closed
-        if (pathClosed) {
-            context.beginPath();
-            pathClosed = false;
-        }
-        
-        if (layer.autosize) fitRect(x, y, w, h);
-        context.rect(x, y, w, h);
-        
-    };
-
-    /**
-     * Clip all future drawing operations by the current path. The current path
-     * is implicitly closed. The current path can continue to be reused
-     * for other operations (such as fillColor()) but a new path will be started
-     * once a path drawing operation (path() or rect()) is used.
-     */
-    this.clip = function() {
-
-        // Set new clipping region
-        context.clip();
-
-        // Path now implicitly closed
-        pathClosed = true;
-
-    };
-
-    /**
-     * Stroke the current path with the specified color. The current path
-     * is implicitly closed. The current path can continue to be reused
-     * for other operations (such as clip()) but a new path will be started
-     * once a path drawing operation (path() or rect()) is used.
-     * 
-     * @param {String} cap The line cap style. Can be "round", "square",
-     *                     or "butt".
-     * @param {String} join The line join style. Can be "round", "bevel",
-     *                      or "miter".
-     * @param {Number} thickness The line thickness in pixels.
-     * @param {Number} r The red component of the color to fill.
-     * @param {Number} g The green component of the color to fill.
-     * @param {Number} b The blue component of the color to fill.
-     * @param {Number} a The alpha component of the color to fill.
-     */
-    this.strokeColor = function(cap, join, thickness, r, g, b, a) {
-
-        // Stroke with color
-        context.lineCap = cap;
-        context.lineJoin = join;
-        context.lineWidth = thickness;
-        context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
-        context.stroke();
-
-        // Path now implicitly closed
-        pathClosed = true;
-
-    };
-
-    /**
-     * Fills the current path with the specified color. The current path
-     * is implicitly closed. The current path can continue to be reused
-     * for other operations (such as clip()) but a new path will be started
-     * once a path drawing operation (path() or rect()) is used.
-     * 
-     * @param {Number} r The red component of the color to fill.
-     * @param {Number} g The green component of the color to fill.
-     * @param {Number} b The blue component of the color to fill.
-     * @param {Number} a The alpha component of the color to fill.
-     */
-    this.fillColor = function(r, g, b, a) {
-
-        // Fill with color
-        context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
-        context.fill();
-
-        // Path now implicitly closed
-        pathClosed = true;
-
-    };
-
-    /**
-     * Stroke the current path with the image within the specified layer. The
-     * image data will be tiled infinitely within the stroke. The current path
-     * is implicitly closed. The current path can continue to be reused
-     * for other operations (such as clip()) but a new path will be started
-     * once a path drawing operation (path() or rect()) is used.
-     * 
-     * @param {String} cap The line cap style. Can be "round", "square",
-     *                     or "butt".
-     * @param {String} join The line join style. Can be "round", "bevel",
-     *                      or "miter".
-     * @param {Number} thickness The line thickness in pixels.
-     * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
-     *                                   within the stroke.
-     */
-    this.strokeLayer = function(cap, join, thickness, srcLayer) {
-
-        // Stroke with image data
-        context.lineCap = cap;
-        context.lineJoin = join;
-        context.lineWidth = thickness;
-        context.strokeStyle = context.createPattern(
-            srcLayer.getCanvas(),
-            "repeat"
-        );
-        context.stroke();
-
-        // Path now implicitly closed
-        pathClosed = true;
-
-    };
-
-    /**
-     * Fills the current path with the image within the specified layer. The
-     * image data will be tiled infinitely within the stroke. The current path
-     * is implicitly closed. The current path can continue to be reused
-     * for other operations (such as clip()) but a new path will be started
-     * once a path drawing operation (path() or rect()) is used.
-     * 
-     * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
-     *                                   within the fill.
-     */
-    this.fillLayer = function(srcLayer) {
-
-        // Fill with image data 
-        context.fillStyle = context.createPattern(
-            srcLayer.getCanvas(),
-            "repeat"
-        );
-        context.fill();
-
-        // Path now implicitly closed
-        pathClosed = true;
-
-    };
-
-    /**
-     * Push current layer state onto stack.
-     */
-    this.push = function() {
-
-        // Save current state onto stack
-        context.save();
-        stackSize++;
-
-    };
-
-    /**
-     * Pop layer state off stack.
-     */
-    this.pop = function() {
-
-        // Restore current state from stack
-        if (stackSize > 0) {
-            context.restore();
-            stackSize--;
-        }
-
-    };
-
-    /**
-     * Reset the layer, clearing the stack, the current path, and any transform
-     * matrix.
-     */
-    this.reset = function() {
-
-        // Clear stack
-        while (stackSize > 0) {
-            context.restore();
-            stackSize--;
-        }
-
-        // Restore to initial state
-        context.restore();
-        context.save();
-
-        // Clear path
-        context.beginPath();
-        pathClosed = false;
-
-    };
-
-    /**
-     * Sets the given affine transform (defined with six values from the
-     * transform's matrix).
-     * 
-     * @param {Number} a The first value in the affine transform's matrix.
-     * @param {Number} b The second value in the affine transform's matrix.
-     * @param {Number} c The third value in the affine transform's matrix.
-     * @param {Number} d The fourth value in the affine transform's matrix.
-     * @param {Number} e The fifth value in the affine transform's matrix.
-     * @param {Number} f The sixth value in the affine transform's matrix.
-     */
-    this.setTransform = function(a, b, c, d, e, f) {
-        context.setTransform(
-            a, b, c,
-            d, e, f
-          /*0, 0, 1*/
-        );
-    };
-
-    /**
-     * Applies the given affine transform (defined with six values from the
-     * transform's matrix).
-     * 
-     * @param {Number} a The first value in the affine transform's matrix.
-     * @param {Number} b The second value in the affine transform's matrix.
-     * @param {Number} c The third value in the affine transform's matrix.
-     * @param {Number} d The fourth value in the affine transform's matrix.
-     * @param {Number} e The fifth value in the affine transform's matrix.
-     * @param {Number} f The sixth value in the affine transform's matrix.
-     */
-    this.transform = function(a, b, c, d, e, f) {
-        context.transform(
-            a, b, c,
-            d, e, f
-          /*0, 0, 1*/
-        );
-    };
-
-    /**
-     * Sets the channel mask for future operations on this Layer.
-     * 
-     * The channel mask is a Guacamole-specific compositing operation identifier
-     * with a single bit representing each of four channels (in order): source
-     * image where destination transparent, source where destination opaque,
-     * destination where source transparent, and destination where source
-     * opaque.
-     * 
-     * @param {Number} mask The channel mask for future operations on this
-     *                      Layer.
-     */
-    this.setChannelMask = function(mask) {
-        context.globalCompositeOperation = compositeOperation[mask];
-    };
-
-    /**
-     * Sets the miter limit for stroke operations using the miter join. This
-     * limit is the maximum ratio of the size of the miter join to the stroke
-     * width. If this ratio is exceeded, the miter will not be drawn for that
-     * joint of the path.
-     * 
-     * @param {Number} limit The miter limit for stroke operations using the
-     *                       miter join.
-     */
-    this.setMiterLimit = function(limit) {
-        context.miterLimit = limit;
-    };
-
-    // Initialize canvas dimensions
-    canvas.width = width;
-    canvas.height = height;
-
-    // Explicitly render canvas below other elements in the layer (such as
-    // child layers). Chrome and others may fail to render layers properly
-    // without this.
-    canvas.style.zIndex = -1;
-
-};
-
-/**
- * Channel mask for the composite operation "rout".
- */
-Guacamole.Layer.ROUT  = 0x2;
-
-/**
- * Channel mask for the composite operation "atop".
- */
-Guacamole.Layer.ATOP  = 0x6;
-
-/**
- * Channel mask for the composite operation "xor".
- */
-Guacamole.Layer.XOR   = 0xA;
-
-/**
- * Channel mask for the composite operation "rover".
- */
-Guacamole.Layer.ROVER = 0xB;
-
-/**
- * Channel mask for the composite operation "over".
- */
-Guacamole.Layer.OVER  = 0xE;
-
-/**
- * Channel mask for the composite operation "plus".
- */
-Guacamole.Layer.PLUS  = 0xF;
-
-/**
- * Channel mask for the composite operation "rin".
- * Beware that WebKit-based browsers may leave the contents of the destionation
- * layer where the source layer is transparent, despite the definition of this
- * operation.
- */
-Guacamole.Layer.RIN   = 0x1;
-
-/**
- * Channel mask for the composite operation "in".
- * Beware that WebKit-based browsers may leave the contents of the destionation
- * layer where the source layer is transparent, despite the definition of this
- * operation.
- */
-Guacamole.Layer.IN    = 0x4;
-
-/**
- * Channel mask for the composite operation "out".
- * Beware that WebKit-based browsers may leave the contents of the destionation
- * layer where the source layer is transparent, despite the definition of this
- * operation.
- */
-Guacamole.Layer.OUT   = 0x8;
-
-/**
- * Channel mask for the composite operation "ratop".
- * Beware that WebKit-based browsers may leave the contents of the destionation
- * layer where the source layer is transparent, despite the definition of this
- * operation.
- */
-Guacamole.Layer.RATOP = 0x9;
-
-/**
- * Channel mask for the composite operation "src".
- * Beware that WebKit-based browsers may leave the contents of the destionation
- * layer where the source layer is transparent, despite the definition of this
- * operation.
- */
-Guacamole.Layer.SRC   = 0xC;
-
-/**
- * Represents a single pixel of image data. All components have a minimum value
- * of 0 and a maximum value of 255.
- * 
- * @constructor
- * 
- * @param {Number} r The red component of this pixel.
- * @param {Number} g The green component of this pixel.
- * @param {Number} b The blue component of this pixel.
- * @param {Number} a The alpha component of this pixel.
- */
-Guacamole.Layer.Pixel = function(r, g, b, a) {
-
-    /**
-     * The red component of this pixel, where 0 is the minimum value,
-     * and 255 is the maximum.
-     */
-    this.red   = r;
-
-    /**
-     * The green component of this pixel, where 0 is the minimum value,
-     * and 255 is the maximum.
-     */
-    this.green = g;
-
-    /**
-     * The blue component of this pixel, where 0 is the minimum value,
-     * and 255 is the maximum.
-     */
-    this.blue  = b;
-
-    /**
-     * The alpha component of this pixel, where 0 is the minimum value,
-     * and 255 is the maximum.
-     */
-    this.alpha = a;
-
-};