hterm 0.8.8.0: Synchronized preferences.

* Add lib.Storage.Local and lib.Storage.Chrome classes to normalize the API
  between window.localStorage and chrome.storage.
* Generalize nassh.PreferenceManager profile management code into
  lib.PreferenceManager so that other code can easily define child preferences.
* Add preference to override the default color palette.
* Remove session-id code.

TEST=test_harness.html, 66/66 tests passed.
BUG=chromium-os:26284, Custom palette.

Change-Id: Ic90343253dcb2235a61fcec73e21833a68022fe8
Reviewed-on: https://gerrit.chromium.org/gerrit/34237
Reviewed-by: Dmitry Polukhin <dpolukhin@chromium.org>
Reviewed-by: Marius Schilder <mschilder@google.com>
Tested-by: Marius Schilder <mschilder@google.com>
Commit-Ready: Robert Ginda <rginda@chromium.org>
Reviewed-by: Robert Ginda <rginda@chromium.org>
Tested-by: Robert Ginda <rginda@chromium.org>
diff --git a/hterm/js/hterm_terminal.js b/hterm/js/hterm_terminal.js
index 33a85e1..a553395 100644
--- a/hterm/js/hterm_terminal.js
+++ b/hterm/js/hterm_terminal.js
@@ -6,8 +6,8 @@
 
 lib.rtdep('lib.colors', 'lib.PreferenceManager',
           'hterm.msg',
-          'hterm.Keyboard', 'hterm.Options', 'hterm.Screen',
-          'hterm.ScrollPort', 'hterm.Size', 'hterm.VT');
+          'hterm.Keyboard', 'hterm.Options', 'hterm.PreferenceManager',
+          'hterm.Screen', 'hterm.ScrollPort', 'hterm.Size', 'hterm.VT');
 
 /**
  * Constructor for the Terminal class.
@@ -26,12 +26,11 @@
  * displayed twice as wide as standard latin characters.  This is to support
  * CJK (and possibly other character sets).
  *
- * @param {string} opt_profileName Optional preference profile name.  If not
+ * @param {string} opt_profileId Optional preference profile name.  If not
  *     provided, defaults to 'default'.
  */
-hterm.Terminal = function(opt_profileName) {
-  this.profileName_ = null;
-  this.setProfile(opt_profileName || 'default');
+hterm.Terminal = function(opt_profileId) {
+  this.profileId_ = null;
 
   // Two screen instances.
   this.primaryScreen_ = new hterm.Screen();
@@ -80,15 +79,14 @@
   this.cursorNode_ = null;
 
   // These prefs are cached so we don't have to read from local storage with
-  // each output and keystroke.
-  this.scrollOnOutput_ = this.prefs_.get('scroll-on-output');
-  this.scrollOnKeystroke_ = this.prefs_.get('scroll-on-keystroke');
-  this.foregroundColor_ = this.prefs_.get('foreground-color');
-  this.backgroundColor_ = this.prefs_.get('background-color');
+  // each output and keystroke.  They are initialized by the preference manager.
+  this.scrollOnOutput_ = null;
+  this.scrollOnKeystroke_ = null;
+  this.foregroundColor_ = null;
+  this.backgroundColor_ = null;
 
   // Terminal bell sound.
   this.bellAudio_ = this.document_.createElement('audio');
-  this.bellAudio_.setAttribute('src', this.prefs_.get('audible-bell-sound'));
   this.bellAudio_.setAttribute('preload', 'auto');
 
   // Cursor position and attributes saved with DECSC.
@@ -102,9 +100,6 @@
 
   // The VT escape sequence interpreter.
   this.vt = new hterm.VT(this);
-  this.vt.enable8BitControl = this.prefs_.get('enable-8-bit-control');
-  this.vt.maxStringSequence = this.prefs_.get('max-string-sequence');
-  this.vt.enableClipboardWrite = this.prefs_.get('enable-clipboard-write');
 
   // The keyboard hander.
   this.keyboard = new hterm.Keyboard(this);
@@ -116,15 +111,26 @@
   // True if mouse-click-drag should scroll the terminal.
   this.enableMouseDragScroll = true;
 
-  this.copyOnSelect = this.prefs_.get('copy-on-select');
+  this.copyOnSelect = null;
   this.mousePasteButton = null;
-  this.syncMousePasteButton();
 
   this.realizeSize_(80, 24);
   this.setDefaultTabStops();
+
+  this.setProfile(opt_profileId || 'default',
+                  function() { this.onTerminalReady() }.bind(this));
 };
 
 /**
+ * Clients should override this to be notified when the terminal is ready
+ * for use.
+ *
+ * The terminal initialization is asynchronous, and shouldn't be used before
+ * this method is called.
+ */
+hterm.Terminal.prototype.onTerminalReady = function() { };
+
+/**
  * Default tab with of 8 to match xterm.
  */
 hterm.Terminal.prototype.tabWidth = 8;
@@ -142,297 +148,170 @@
  *
  * @param {string} newName The name of the preference profile.  Forward slash
  *     characters will be removed from the name.
+ * @param {function} opt_callback Optional callback to invoke when the profile
+ *     transition is complete.
  */
-hterm.Terminal.prototype.setProfile = function(profileName) {
-  // If we already have a profile selected, we're going to need to re-sync
-  // with the new profile.
-  var needSync = !!this.profileName_;
+hterm.Terminal.prototype.setProfile = function(profileId, opt_callback) {
+  this.profileId_ = profileId.replace(/\//g, '');
 
-  this.profileName_ = profileName.replace(/\//g, '');
+  var terminal = this;
 
-  this.prefs_ = new lib.PreferenceManager(
-      '/hterm/prefs/profiles/' + this.profileName_);
+  if (this.prefs_)
+    this.prefs_.deactivate();
 
-  var self = this;
-  this.prefs_.definePreferences
-  ([
-    /**
-     * Set whether the alt key acts as a meta key or as a distinct alt key.
-     */
-    ['alt-is-meta', false, function(v) {
-        self.keyboard.altIsMeta = v;
+  this.prefs_ = new hterm.PreferenceManager(this.profileId_);
+  this.prefs_.addObservers(null, {
+    'alt-is-meta': function(v) {
+      terminal.keyboard.altIsMeta = v;
+    },
+
+    'alt-sends-what': function(v) {
+      if (!/^(escape|8-bit|browser-key)$/.test(v))
+        v = 'escape';
+
+      terminal.keyboard.altSendsWhat = v;
+    },
+
+    'audible-bell-sound': function(v) {
+      terminal.bellAudio_.setAttribute('src', v);
+    },
+
+    'background-color': function(v) {
+      terminal.setBackgroundColor(v);
+    },
+
+    'background-image': function(v) {
+      terminal.scrollPort_.setBackgroundImage(v);
+    },
+
+    'background-size': function(v) {
+      terminal.scrollPort_.setBackgroundSize(v);
+    },
+
+    'background-position': function(v) {
+      terminal.scrollPort_.setBackgroundPosition(v);
+    },
+
+    'backspace-sends-backspace': function(v) {
+      terminal.keyboard.backspaceSendsBackspace = v;
+    },
+
+    'cursor-blink': function(v) {
+      terminal.setCursorBlink(!!v);
+    },
+
+    'cursor-color': function(v) {
+      terminal.setCursorColor(v);
+    },
+
+    'color-palette-overrides': function(v) {
+      if (!(v == null || v instanceof Object || v instanceof Array)) {
+        console.warn('Preference color-palette-overrides is not an array or ' +
+                     'object: ' + v);
+        return;
       }
-    ],
 
-    /**
-     * Controls how the alt key is handled.
-     *
-     *  escape....... Send an ESC prefix.
-     *  8-bit........ Add 128 to the unshifted character as in xterm.
-     *  browser-key.. Wait for the keypress event and see what the browser says.
-     *                (This won't work well on platforms where the browser
-     *                 performs a default action for some alt sequences.)
-     */
-    ['alt-sends-what', 'escape', function(v) {
-        if (!/^(escape|8-bit|browser-key)$/.test(v))
-          v = 'escape';
+      lib.colors.colorPalette = lib.colors.stockColorPalette.concat();
 
-        self.keyboard.altSendsWhat = v;
+      if (v) {
+        for (var key in v) {
+          var i = parseInt(key);
+          if (isNaN(i) || i < 0 || i > 255) {
+            console.log('Invalid value in palette: ' + key + ': ' + v[key]);
+            continue;
+          }
+
+          if (v[i]) {
+            var rgb = lib.colors.normalizeCSS(v[i]);
+            if (rgb)
+              lib.colors.colorPalette[i] = rgb;
+          }
+        }
       }
-    ],
 
-    /**
-     * Terminal bell sound.  Empty string for no audible bell.
-     */
-    ['audible-bell-sound', '../audio/bell.ogg', function(v) {
-        self.bellAudio_.setAttribute('src', v);
-      }
-    ],
+      terminal.primaryScreen_.textAttributes.resetColorPalette()
+      terminal.alternateScreen_.textAttributes.resetColorPalette();
+    },
 
-    /**
-     * The background color for text with no other color attributes.
-     */
-    ['background-color', 'rgb(16, 16, 16)', function(v) {
-        self.setBackgroundColor(v);
-      }
-    ],
+    'copy-on-select': function(v) {
+      terminal.copyOnSelect = !!v;
+    },
 
-    /**
-     * The background image.
-     */
-    ['background-image', '',
-     function(v) {
-        self.scrollPort_.setBackgroundImage(v);
-      }
-    ],
+    'enable-8-bit-control': function(v) {
+      terminal.vt.enable8BitControl = !!v;
+    },
 
-    /**
-     * The background image size,
-     *
-     * Defaults to none.
-     */
-    ['background-size', '', function(v) {
-        self.scrollPort_.setBackgroundSize(v);
-      }
-    ],
+    'enable-bold': function(v) {
+      terminal.syncBoldSafeState();
+    },
 
-    /**
-     * The background image position,
-     *
-     * Defaults to none.
-     */
-    ['background-position', '', function(v) {
-        self.scrollPort_.setBackgroundPosition(v);
-      }
-    ],
+    'enable-clipboard-write': function(v) {
+      terminal.vt.enableClipboardWrite = !!v;
+    },
 
-    /**
-     * If true, the backspace should send BS ('\x08', aka ^H).  Otherwise
-     * the backspace key should send '\x7f'.
-     */
-    ['backspace-sends-backspace', false, function(v) {
-        self.keyboard.backspaceSendsBackspace = v;
-      }
-    ],
+    'font-family': function(v) {
+      terminal.syncFontFamily();
+    },
 
-    /**
-     * Whether or not to close the window when the command exits.
-     */
-    ['close-on-exit', true, null],
+    'font-size': function(v) {
+      terminal.setFontSize(v);
+    },
 
-    /**
-     * Whether or not to blink the cursor by default.
-     */
-    ['cursor-blink', false, function(v) {
-        self.setCursorBlink(!!v);
-      }
-    ],
+    'font-smoothing': function(v) {
+      terminal.syncFontFamily();
+    },
 
-    /**
-     * The color of the visible cursor.
-     */
-    ['cursor-color', 'rgba(255,0,0,0.5)', function(v) {
-        self.setCursorColor(v);
-      }
-    ],
+    'foreground-color': function(v) {
+      terminal.setForegroundColor(v);
+    },
 
-    /**
-     * Automatically copy mouse selection to the clipboard.
-     */
-    ['copy-on-select', true, function(v) {
-        self.copyOnSelect = !!v;
-      }
-    ],
+    'home-keys-scroll': function(v) {
+      terminal.keyboard.homeKeysScroll = v;
+    },
 
-    /**
-     * True to enable 8-bit control characters, false to ignore them.
-     *
-     * We'll respect the two-byte versions of these control characters
-     * regardless of this setting.
-     */
-    ['enable-8-bit-control', false, function(v) {
-        self.vt.enable8BitControl = !!v;
-      }
-    ],
+    'max-string-sequence': function(v) {
+      terminal.vt.maxStringSequence = v;
+    },
 
-    /**
-     * True if we should use bold weight font for text with the bold/bright
-     * attribute.  False to use bright colors only.  Null to autodetect.
-     */
-    ['enable-bold', null, function(v) {
-        self.syncBoldSafeState();
-      }
-    ],
+    'meta-sends-escape': function(v) {
+      terminal.keyboard.metaSendsEscape = v;
+    },
 
-    /**
-     * Allow the host to write directly to the system clipboard.
-     */
-    ['enable-clipboard-notice', true, null],
+    'mouse-cell-motion-trick': function(v) {
+      terminal.vt.setMouseCellMotionTrick(v);
+    },
 
-    /**
-     * Allow the host to write directly to the system clipboard.
-     */
-    ['enable-clipboard-write', true, function(v) {
-        self.vt.enableClipboardWrite = !!v;
-      }
-    ],
+    'mouse-paste-button': function(v) {
+      terminal.syncMousePasteButton();
+    },
 
-    /**
-     * Default font family for the terminal text.
-     */
-    ['font-family', ('"DejaVu Sans Mono", "Everson Mono", ' +
-                     'FreeMono, "Menlo", "Terminal", ' +
-                     'monospace'),
-     function(v) { self.syncFontFamily() }
-    ],
+    'scroll-on-keystroke': function(v) {
+      terminal.scrollOnKeystroke_ = v;
+    },
 
-    /**
-     * The default font size in pixels.
-     */
-    ['font-size', 15, function(v) {
-        self.setFontSize(v);
-      }
-    ],
+    'scroll-on-output': function(v) {
+      terminal.scrollOnOutput_ = v;
+    },
 
-    /**
-     * Anti-aliasing.
-     */
-    ['font-smoothing', 'antialiased',
-     function(v) { self.syncFontFamily() }
-    ],
+    'scrollbar-visible': function(v) {
+      terminal.setScrollbarVisible(v);
+    },
 
-    /**
-     * The foreground color for text with no other color attributes.
-     */
-    ['foreground-color', 'rgb(240, 240, 240)', function(v) {
-        self.setForegroundColor(v);
-      }
-    ],
+    'shift-insert-paste': function(v) {
+      terminal.keyboard.shiftInsertPaste = v;
+    },
 
-    /**
-     * If true, home/end will control the terminal scrollbar and shift home/end
-     * will send the VT keycodes.  If false then home/end sends VT codes and
-     * shift home/end scrolls.
-     */
-    ['home-keys-scroll', false, function(v) {
-        self.keyboard.homeKeysScroll = v;
-      }
-    ],
+    'page-keys-scroll': function(v) {
+      terminal.keyboard.pageKeysScroll = v;
+    }
+  });
 
-    /**
-     * Max length of a DCS, OSC, PM, or APS sequence before we give up and
-     * ignore the code.
-     */
-    ['max-string-sequence', 100000, function(v) {
-        self.vt.maxStringSequence = v;
-      }
-    ],
-
-    /**
-     * Set whether the meta key sends a leading escape or not.
-     */
-    ['meta-sends-escape', true, function(v) {
-        self.keyboard.metaSendsEscape = v;
-      }
-    ],
-
-    /**
-     * Set whether we should treat DEC mode 1002 (mouse cell motion tracking)
-     * as if it were 1000 (mouse click tracking).
-     *
-     * This makes it possible to use vi's ":set mouse=a" mode without losing
-     * access to the system text selection mechanism.
-     */
-    ['mouse-cell-motion-trick', false, function(v) {
-        self.vt.setMouseCellMotionTrick(v);
-      }
-    ],
-
-    /**
-     * Mouse paste button, or null to autodetect.
-     *
-     * For autodetect, we'll try to enable middle button paste for non-X11
-     * platforms.
-     *
-     * On X11 we move it to button 3, but that'll probably be a context menu
-     * in the future.
-     */
-    ['mouse-paste-button', null, function(v) {
-        self.syncMousePasteButton();
-      }
-    ],
-
-    /**
-     * If true, scroll to the bottom on any keystroke.
-     */
-    ['scroll-on-keystroke', true, function(v) {
-        self.scrollOnKeystroke_ = v;
-      }
-    ],
-
-    /**
-     * If true, scroll to the bottom on terminal output.
-     */
-    ['scroll-on-output', false, function(v) {
-        self.scrollOnOutput_ = v;
-      }
-    ],
-
-    /**
-     * The vertical scrollbar mode.
-     */
-    ['scrollbar-visible', true, function(v) {
-        self.setScrollbarVisible(v);
-      }
-    ],
-
-    /**
-     * Shift + Insert pastes if true, sent to host if false.
-     */
-    ['shift-insert-paste', true, function(v) {
-        self.keyboard.shiftInsertPaste = v;
-      }
-    ],
-
-    /**
-     * The default environment variables.
-     */
-    ['environment', {TERM: 'xterm-256color'}, null],
-
-    /**
-     * If true, page up/down will control the terminal scrollbar and shift
-     * page up/down will send the VT keycodes.  If false then page up/down
-     * sends VT codes and shift page up/down scrolls.
-     */
-    ['page-keys-scroll', false, function(v) {
-        self.keyboard.pageKeysScroll = v;
-      }
-    ],
-
-   ]);
-
-  if (needSync)
+  this.prefs_.readStorage(function() {
     this.prefs_.notifyAll();
+
+    if (opt_callback)
+      opt_callback();
+  }.bind(this));
 };
 
 
@@ -470,6 +349,10 @@
  */
 hterm.Terminal.prototype.setBackgroundColor = function(color) {
   this.backgroundColor_ = lib.colors.normalizeCSS(color);
+  this.primaryScreen_.textAttributes.setDefaults(
+      this.foregroundColor_, this.backgroundColor_);
+  this.alternateScreen_.textAttributes.setDefaults(
+      this.foregroundColor_, this.backgroundColor_);
   this.scrollPort_.setBackgroundColor(color);
 };
 
@@ -491,6 +374,10 @@
  */
 hterm.Terminal.prototype.setForegroundColor = function(color) {
   this.foregroundColor_ = lib.colors.normalizeCSS(color);
+  this.primaryScreen_.textAttributes.setDefaults(
+      this.foregroundColor_, this.backgroundColor_);
+  this.alternateScreen_.textAttributes.setDefaults(
+      this.foregroundColor_, this.backgroundColor_);
   this.scrollPort_.setForegroundColor(color);
 };