GUACAMOLE-630: Tolerate insufficient JavaScript features to run "Pickr" color picker (Internet Explorer).
diff --git a/guacamole/src/main/webapp/app/form/directives/guacInputColor.js b/guacamole/src/main/webapp/app/form/directives/guacInputColor.js
index 24ab501..3b010cc 100644
--- a/guacamole/src/main/webapp/app/form/directives/guacInputColor.js
+++ b/guacamole/src/main/webapp/app/form/directives/guacInputColor.js
@@ -19,36 +19,13 @@
 
 /**
  * A directive which implements a color input field, leveraging the "Pickr"
- * color picker.
+ * color picker. If the "Picker" color picker cannot be used because it relies
+ * on JavaScript features not supported by the browser (Internet Explorer), a
+ * "guacInputColorUnavailable" event will be emitted up the scope, and this
+ * directive will become read-only, functioning essentially as a color preview.
  */
 angular.module('form').directive('guacInputColor', [function guacInputColor() {
 
-    /**
-     * Returns whether the given color is relatively dark. A color is
-     * considered dark if white text would be more visible over a background
-     * of that color (provide better contrast) than black text.
-     *
-     * @param {HSVaColor} color
-     *     The color to test.
-     *
-     * @returns {Boolean}
-     *     true if the given color is relatively dark (white text would provide
-     *     better contrast than black), false otherwise.
-     */
-    var isDark = function isDark(color) {
-
-        var rgb = color.toRGBA();
-
-        // Convert RGB to luminance in HSL space (as defined by the
-        // relative luminance formula given by the W3C for accessibility)
-        var luminance = 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
-
-        // Consider the background to be dark if white text over that
-        // background would provide better contrast than black
-        return luminance <= 153; // 153 is the component value 0.6 converted from 0-1 to the 0-255 range
-
-    };
-
     var config = {
         restrict: 'E',
         replace: true,
@@ -86,13 +63,44 @@
         var $translate = $injector.get('$translate');
 
         /**
-         * Whether the color currently selected is "dark" in the sense that the
-         * color white will have higher contrast against it than the color
-         * black.
+         * Whether the color picker ("Pickr") cannot be used. In general, all
+         * browsers should support Pickr with the exception of Internet
+         * Explorer.
          *
          * @type Boolean
          */
-        $scope.dark = false;
+        $scope.colorPickerUnavailable = false;
+
+        /**
+         * Returns whether the color currently selected is "dark" in the sense
+         * that the color white will have higher contrast against it than the
+         * color black.
+         *
+         * @returns {Boolean}
+         *     true if the currently selected color is relatively dark (white
+         *     text would provide better contrast than black), false otherwise.
+         */
+        $scope.isDark = function isDark() {
+
+            // Assume not dark if color is invalid or undefined
+            var rgb = $scope.model && /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec($scope.model);
+            if (!rgb)
+                return false;
+
+            // Parse color component values as hexadecimal
+            var red = parseInt(rgb[1], 16);
+            var green = parseInt(rgb[2], 16);
+            var blue = parseInt(rgb[3], 16);
+
+            // Convert RGB to luminance in HSL space (as defined by the
+            // relative luminance formula given by the W3C for accessibility)
+            var luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
+
+            // Consider the background to be dark if white text over that
+            // background would provide better contrast than black
+            return luminance <= 153; // 153 is the component value 0.6 converted from 0-1 to the 0-255 range
+
+        };
 
         // Init color picker after required translation strings are available
         $q.all({
@@ -100,81 +108,90 @@
             'cancel' : $translate('APP.ACTION_CANCEL')
         }).then(function stringsRetrieved(strings) {
 
-            /**
-             * An instance of the "Pickr" color picker, bound to the underlying
-             * element of this directive.
-             *
-             * @type Pickr
-             */
-            var pickr = Pickr.create({
+            try {
 
-                // Bind color picker to the underlying element of this directive
-                el : $element[0],
+                /**
+                 * An instance of the "Pickr" color picker, bound to the underlying
+                 * element of this directive.
+                 *
+                 * @type Pickr
+                 */
+                var pickr = Pickr.create({
 
-                // Wrap color picker dialog in Guacamole-specific class for
-                // sake of additional styling
-                appClass : 'guac-input-color-picker',
+                    // Bind color picker to the underlying element of this directive
+                    el : $element[0],
 
-                // Display color details as hex
-                defaultRepresentation : 'HEX',
+                    // Wrap color picker dialog in Guacamole-specific class for
+                    // sake of additional styling
+                    appClass : 'guac-input-color-picker',
 
-                // Use "monolith" theme, as a nice balance between "nano" (does
-                // not work in Internet Explorer) and "classic" (too big)
-                theme : 'monolith',
+                    // Display color details as hex
+                    defaultRepresentation : 'HEX',
 
-                // Leverage the container element as the button which shows the
-                // picker, relying on our own styling for that button
-                useAsButton  : true,
-                appendToBody : true,
+                    // Use "monolith" theme, as a nice balance between "nano" (does
+                    // not work in Internet Explorer) and "classic" (too big)
+                    theme : 'monolith',
 
-                // Do not include opacity controls
-                lockOpacity : true,
+                    // Leverage the container element as the button which shows the
+                    // picker, relying on our own styling for that button
+                    useAsButton  : true,
+                    appendToBody : true,
 
-                // Include a selection of palette entries for convenience and
-                // reference
-                swatches : $scope.palette || [],
+                    // Do not include opacity controls
+                    lockOpacity : true,
 
-                components: {
+                    // Include a selection of palette entries for convenience and
+                    // reference
+                    swatches : $scope.palette || [],
 
-                    // Include hue and color preview controls
-                    preview : true,
-                    hue     : true,
+                    components: {
 
-                    // Display only a text color input field and the save and
-                    // cancel buttons (no clear button)
-                    interaction: {
-                        input  : true,
-                        save   : true,
-                        cancel : true
-                    }
+                        // Include hue and color preview controls
+                        preview : true,
+                        hue     : true,
 
-                },
+                        // Display only a text color input field and the save and
+                        // cancel buttons (no clear button)
+                        interaction: {
+                            input  : true,
+                            save   : true,
+                            cancel : true
+                        }
 
-                // Use translation strings for buttons
-                strings : strings
+                    },
 
-            });
+                    // Use translation strings for buttons
+                    strings : strings
 
-            // Hide color picker after user clicks "cancel"
-            pickr.on('cancel', function colorChangeCanceled() {
-                pickr.hide();
-            });
-
-            // Keep model in sync with changes to the color picker
-            pickr.on('save', function colorChanged(color) {
-                $scope.$evalAsync(function updateModel() {
-                    $scope.model = color.toHEXA().toString();
-                    $scope.dark = isDark(pickr.getColor());
                 });
-            });
 
-            // Keep color picker in sync with changes to the model
-            pickr.on('init', function pickrReady(color) {
-                $scope.$watch('model', function modelChanged(model) {
-                    pickr.setColor(model);
-                    $scope.dark = isDark(pickr.getColor());
+                // Hide color picker after user clicks "cancel"
+                pickr.on('cancel', function colorChangeCanceled() {
+                    pickr.hide();
                 });
-            });
+
+                // Keep model in sync with changes to the color picker
+                pickr.on('save', function colorChanged(color) {
+                    $scope.$evalAsync(function updateModel() {
+                        $scope.model = color.toHEXA().toString();
+                    });
+                });
+
+                // Keep color picker in sync with changes to the model
+                pickr.on('init', function pickrReady(color) {
+                    $scope.$watch('model', function modelChanged(model) {
+                        pickr.setColor(model);
+                    });
+                });
+
+            }
+
+            // If the "Pickr" color picker cannot be loaded (Internet Explorer),
+            // let the scope above us know
+            catch (e) {
+                $scope.colorPickerUnavailable = true;
+                $scope.$emit('guacInputColorUnavailable', e);
+            }
 
         }, angular.noop);
 
diff --git a/guacamole/src/main/webapp/app/form/styles/terminal-color-scheme-field.css b/guacamole/src/main/webapp/app/form/styles/terminal-color-scheme-field.css
index a4b0cb4..896273c 100644
--- a/guacamole/src/main/webapp/app/form/styles/terminal-color-scheme-field.css
+++ b/guacamole/src/main/webapp/app/form/styles/terminal-color-scheme-field.css
@@ -67,6 +67,10 @@
     cursor: pointer;
 }
 
+.form-field .terminal-color-scheme-field .custom-color-scheme .guac-input-color.read-only {
+    cursor: not-allowed;
+}
+
 /*
  * Color button font colors
  */
diff --git a/guacamole/src/main/webapp/app/form/templates/guacInputColor.html b/guacamole/src/main/webapp/app/form/templates/guacInputColor.html
index 35af778..fc6e675 100644
--- a/guacamole/src/main/webapp/app/form/templates/guacInputColor.html
+++ b/guacamole/src/main/webapp/app/form/templates/guacInputColor.html
@@ -1,3 +1,10 @@
-<div class="guac-input-color" ng-class="{ 'dark' : dark }" ng-style="{ 'background-color' : model }">
+<div class="guac-input-color"
+     ng-class="{
+         'dark' : isDark(),
+         'read-only' : colorPickerUnavailable
+     }"
+     ng-style="{
+        'background-color' : model
+     }">
     <ng-transclude></ng-transclude>
 </div>
\ No newline at end of file