Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static/js/graveyard/common.js b/static/js/graveyard/common.js
new file mode 100644
index 0000000..621a626
--- /dev/null
+++ b/static/js/graveyard/common.js
@@ -0,0 +1,709 @@
+/* Copyright 2016 The Chromium Authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+// ------------------------------------------------------------------------
+// This file contains common utilities and basic javascript infrastructure.
+//
+// Notes:
+// * Press 'D' to toggle debug mode.
+//
+// Functions:
+//
+// - Assertions
+// DEPRECATED: Use assert.js
+// AssertTrue(): assert an expression. Throws an exception if false.
+// Fail(): Throws an exception. (Mark block of code that should be unreachable)
+// AssertEquals(): assert that two values are equal.
+// AssertType(): assert that a value has a particular type
+//
+// - Cookies
+// SetCookie(): Sets a cookie.
+// ExpireCookie(): Expires a cookie.
+// GetCookie(): Gets a cookie value.
+//
+// - Dynamic HTML/DOM utilities
+// MaybeGetElement(): get an element by its id
+// GetElement(): get an element by its id
+// GetParentNode(): Get the parent of an element
+// GetAttribute(): Get attribute value of a DOM node
+// GetInnerHTML(): get the inner HTML of a node
+// SetCssStyle(): Sets a CSS property of a node.
+// GetStyleProperty(): Get CSS property from a style attribute string
+// GetCellIndex(): Get the index of a table cell in a table row
+// ShowElement(): Show/hide element by setting the "display" css property.
+// ShowBlockElement(): Show/hide block element
+// SetButtonText(): Set the text of a button element.
+// AppendNewElement(): Create and append a html element to a parent node.
+// CreateDIV(): Create a DIV element and append to the document.
+// HasClass(): check if element has a given class
+// AddClass(): add a class to an element
+// RemoveClass(): remove a class from an element
+//
+// - Window/Screen utiltiies
+// GetPageOffsetLeft(): get the X page offset of an element
+// GetPageOffsetTop(): get the Y page offset of an element
+// GetPageOffset(): get the X and Y page offsets of an element
+// GetPageOffsetRight() : get X page offset of the right side of an element
+// GetPageOffsetRight() : get Y page offset of the bottom of an element
+// GetScrollTop(): get the vertical scrolling pos of a window.
+// GetScrollLeft(): get the horizontal scrolling pos of a window
+// IsScrollAtEnd():  check if window scrollbar has reached its maximum offset
+// ScrollTo(): scroll window to a position
+// ScrollIntoView(): scroll window so that an element is in view.
+// GetWindowWidth(): get width of a window.
+// GetWindowHeight(): get height of a window
+// GetAvailScreenWidth(): get available screen width
+// GetAvailScreenHeight(): get available screen height
+// GetNiceWindowHeight(): get a nice height for a new browser window.
+// Open{External/Internal}Window(): open a separate window
+// CloseWindow(): close a window
+//
+// - DOM walking utilities
+// AnnotateTerms(): find terms in a node and decorate them with some tag
+// AnnotateText(): find terms in a text node and decorate them with some tag
+//
+// - String utilties
+// HtmlEscape(): html escapes a string
+// HtmlUnescape(): remove html-escaping.
+// QuoteEscape(): escape " quotes.
+// CollapseWhitespace(): collapse multiple whitespace into one whitespace.
+// Trim(): trim whitespace on ends of string
+// IsEmpty(): check if CollapseWhiteSpace(String) == ""
+// IsLetterOrDigit(): check if a character is a letter or a digit
+// ConvertEOLToLF(): normalize the new-lines of a string.
+// HtmlEscapeInsertWbrs(): HtmlEscapes and inserts <wbr>s (word break tags)
+//   after every n non-space chars and/or after or before certain special chars
+//
+// - TextArea utilities
+// GetCursorPos(): finds the cursor position of a textfield
+// SetCursorPos(): sets the cursor position in a textfield
+//
+// - Array utilities
+// FindInArray(): do a linear search to find an element value.
+// DeleteArrayElement(): return a new array with a specific value removed.
+// CloneObject(): clone an object, copying its values recursively.
+// CloneEvent(): clone an event; cannot use CloneObject because it
+//               suffers from infinite recursion
+//
+// - Formatting utilities
+// PrintArray(): used to print/generate HTML by combining static text
+// and dynamic strings.
+// ImageHtml(): create html for an img tag
+// FormatJSLink(): formats a link that invokes js code when clicked.
+// MakeId3(): formats an id that has two id numbers, eg, foo_3_7
+//
+// - Timeouts
+// SafeTimeout(): sets a timeout with protection against ugly JS-errors
+// CancelTimeout(): cancels a timeout with a given ID
+// CancelAllTimeouts(): cancels all timeouts on a given window
+//
+// - Miscellaneous
+// IsDefined(): returns true if argument is not undefined
+// ------------------------------------------------------------------------
+
+// browser detection
+function BR_AgentContains_(str) {
+  if (str in BR_AgentContains_cache_) {
+    return BR_AgentContains_cache_[str];
+  }
+
+  return BR_AgentContains_cache_[str] =
+    (navigator.userAgent.toLowerCase().indexOf(str) != -1);
+}
+// We cache the results of the indexOf operation. This gets us a 10x benefit in
+// Gecko, 8x in Safari and 4x in MSIE for all of the browser checks
+var BR_AgentContains_cache_ = {};
+
+function BR_IsIE() {
+  return (BR_AgentContains_('msie') || BR_AgentContains_('trident')) &&
+         !window.opera;
+}
+
+function BR_IsKonqueror() {
+  return BR_AgentContains_('konqueror');
+}
+
+function BR_IsSafari() {
+  return BR_AgentContains_('safari') || BR_IsKonqueror();
+}
+
+function BR_IsNav() {
+  return !BR_IsIE() &&
+         !BR_IsSafari() &&
+         BR_AgentContains_('mozilla');
+}
+
+var BACKSPACE_KEYNAME = 'Backspace';
+var COMMA_KEYNAME = ',';
+var DELETE_KEYNAME = 'Delete';
+var UP_KEYNAME = 'ArrowUp';
+var DOWN_KEYNAME = 'ArrowDown';
+var LEFT_KEYNAME = 'ArrowLeft';
+var RIGHT_KEYNAME = 'ArrowRight';
+var ENTER_KEYNAME = 'Enter';
+var ESC_KEYNAME = 'Escape';
+var SPACE_KEYNAME = ' ';
+var TAB_KEYNAME = 'Tab';
+var SHIFT_KEYNAME = 'Shift';
+var PAGE_DOWN_KEYNAME = 'PageDown';
+var PAGE_UP_KEYNAME = 'PageUp';
+
+var MAX_EMAIL_ADDRESS_LENGTH = 320; // 64 + '@' + 255
+var MAX_SIGNATURE_LENGTH = 1000; // 1000 chars of maximum signature
+
+// ------------------------------------------------------------------------
+// Assertions
+// DEPRECATED: Use assert.js
+// ------------------------------------------------------------------------
+/**
+ * DEPRECATED: Use assert.js
+ */
+function raise(msg) {
+  if (typeof Error != 'undefined') {
+    throw new Error(msg || 'Assertion Failed');
+  } else {
+    throw (msg);
+  }
+}
+
+/**
+ * DEPRECATED: Use assert.js
+ *
+ * Fail() is useful for marking logic paths that should
+ * not be reached. For example, if you have a class that uses
+ * ints for enums:
+ *
+ * MyClass.ENUM_FOO = 1;
+ * MyClass.ENUM_BAR = 2;
+ * MyClass.ENUM_BAZ = 3;
+ *
+ * And a switch statement elsewhere in your code that
+ * has cases for each of these enums, then you can
+ * "protect" your code as follows:
+ *
+ * switch(type) {
+ *   case MyClass.ENUM_FOO: doFooThing(); break;
+ *   case MyClass.ENUM_BAR: doBarThing(); break;
+ *   case MyClass.ENUM_BAZ: doBazThing(); break;
+ *   default:
+ *     Fail("No enum in MyClass with value: " + type);
+ * }
+ *
+ * This way, if someone introduces a new value for this enum
+ * without noticing this switch statement, then the code will
+ * fail if the logic allows it to reach the switch with the
+ * new value, alerting the developer that they should add a
+ * case to the switch to handle the new value they have introduced.
+ *
+ * @param {string} opt_msg to display for failure
+ *                 DEFAULT: "Assertion failed"
+ */
+function Fail(opt_msg) {
+  opt_msg = opt_msg || 'Assertion failed';
+  if (IsDefined(DumpError)) DumpError(opt_msg + '\n');
+  raise(opt_msg);
+}
+
+/**
+ * DEPRECATED: Use assert.js
+ *
+ * Asserts that an expression is true (non-zero and non-null).
+ *
+ * Note that it is critical not to pass logic
+ * with side-effects as the expression for AssertTrue
+ * because if the assertions are removed by the
+ * JSCompiler, then the expression will be removed
+ * as well, in which case the side-effects will
+ * be lost. So instead of this:
+ *
+ *  AssertTrue( criticalComputation() );
+ *
+ * Do this:
+ *
+ *  var result = criticalComputation();
+ *  AssertTrue(result);
+ *
+ * @param expression to evaluate
+ * @param {string} opt_msg to display if the assertion fails
+ *
+ */
+function AssertTrue(expression, opt_msg) {
+  if (!expression) {
+    opt_msg = opt_msg || 'Assertion failed';
+    Fail(opt_msg);
+  }
+}
+
+/**
+ * DEPRECATED: Use assert.js
+ *
+ * Asserts that a value is of the provided type.
+ *
+ *   AssertType(6, Number);
+ *   AssertType("ijk", String);
+ *   AssertType([], Array);
+ *   AssertType({}, Object);
+ *   AssertType(ICAL_Date.now(), ICAL_Date);
+ *
+ * @param value
+ * @param type A constructor function
+ * @param {string} opt_msg to display if the assertion fails
+ */
+function AssertType(value, type, opt_msg) {
+  // for backwards compatability only
+  if (typeof value == type) return;
+
+  if (value || value == '') {
+    try {
+      if (type == AssertTypeMap[typeof value] || value instanceof type) return;
+    } catch (e) {/* failure, type was an illegal argument to instanceof */}
+  }
+  let makeMsg = opt_msg === undefined;
+  if (makeMsg) {
+    if (typeof type == 'function') {
+      let match = type.toString().match(/^\s*function\s+([^\s\{]+)/);
+      if (match) type = match[1];
+    }
+    opt_msg = 'AssertType failed: <' + value + '> not typeof '+ type;
+  }
+  Fail(opt_msg);
+}
+
+var AssertTypeMap = {
+  'string': String,
+  'number': Number,
+  'boolean': Boolean,
+};
+
+var EXPIRED_COOKIE_VALUE = 'EXPIRED';
+
+
+// ------------------------------------------------------------------------
+// Window/screen utilities
+// TODO: these should be renamed (e.g. GetWindowWidth to GetWindowInnerWidth
+// and moved to geom.js)
+// ------------------------------------------------------------------------
+// Get page offset of an element
+function GetPageOffsetLeft(el) {
+  let x = el.offsetLeft;
+  if (el.offsetParent != null) {
+    x += GetPageOffsetLeft(el.offsetParent);
+  }
+  return x;
+}
+
+// Get page offset of an element
+function GetPageOffsetTop(el) {
+  let y = el.offsetTop;
+  if (el.offsetParent != null) {
+    y += GetPageOffsetTop(el.offsetParent);
+  }
+  return y;
+}
+
+// Get page offset of an element
+function GetPageOffset(el) {
+  let x = el.offsetLeft;
+  let y = el.offsetTop;
+  if (el.offsetParent != null) {
+    let pos = GetPageOffset(el.offsetParent);
+    x += pos.x;
+    y += pos.y;
+  }
+  return {x: x, y: y};
+}
+
+// Get the y position scroll offset.
+function GetScrollTop(win) {
+  return GetWindowPropertyByBrowser_(win, getScrollTopGetters_);
+}
+
+var getScrollTopGetters_ = {
+  ieQuirks_: function(win) {
+    return win.document.body.scrollTop;
+  },
+  ieStandards_: function(win) {
+    return win.document.documentElement.scrollTop;
+  },
+  dom_: function(win) {
+    return win.pageYOffset;
+  },
+};
+
+// Get the x position scroll offset.
+function GetScrollLeft(win) {
+  return GetWindowPropertyByBrowser_(win, getScrollLeftGetters_);
+}
+
+var getScrollLeftGetters_ = {
+  ieQuirks_: function(win) {
+    return win.document.body.scrollLeft;
+  },
+  ieStandards_: function(win) {
+    return win.document.documentElement.scrollLeft;
+  },
+  dom_: function(win) {
+    return win.pageXOffset;
+  },
+};
+
+// Scroll so that as far as possible the entire element is in view.
+var ALIGN_BOTTOM = 'b';
+var ALIGN_MIDDLE = 'm';
+var ALIGN_TOP = 't';
+
+var getWindowWidthGetters_ = {
+  ieQuirks_: function(win) {
+    return win.document.body.clientWidth;
+  },
+  ieStandards_: function(win) {
+    return win.document.documentElement.clientWidth;
+  },
+  dom_: function(win) {
+    return win.innerWidth;
+  },
+};
+
+function GetWindowHeight(win) {
+  return GetWindowPropertyByBrowser_(win, getWindowHeightGetters_);
+}
+
+var getWindowHeightGetters_ = {
+  ieQuirks_: function(win) {
+    return win.document.body.clientHeight;
+  },
+  ieStandards_: function(win) {
+    return win.document.documentElement.clientHeight;
+  },
+  dom_: function(win) {
+    return win.innerHeight;
+  },
+};
+
+/**
+ * Allows the easy use of different getters for IE quirks mode, IE standards
+ * mode and fully DOM-compliant browers.
+ *
+ * @param win window to get the property for
+ * @param getters object with various getters. Invoked with the passed window.
+ * There are three properties:
+ * - ieStandards_: IE 6.0 standards mode
+ * - ieQuirks_: IE 6.0 quirks mode and IE 5.5 and older
+ * - dom_: Mozilla, Safari and other fully DOM compliant browsers
+ *
+ * @private
+ */
+function GetWindowPropertyByBrowser_(win, getters) {
+  try {
+    if (BR_IsSafari()) {
+      return getters.dom_(win);
+    } else if (!window.opera &&
+               'compatMode' in win.document &&
+               win.document.compatMode == 'CSS1Compat') {
+      return getters.ieStandards_(win);
+    } else if (BR_IsIE()) {
+      return getters.ieQuirks_(win);
+    }
+  } catch (e) {
+    // Ignore for now and fall back to DOM method
+  }
+
+  return getters.dom_(win);
+}
+
+function GetAvailScreenWidth(win) {
+  return win.screen.availWidth;
+}
+
+// Used for horizontally centering a new window of the given width in the
+// available screen. Set the new window's distance from the left of the screen
+// equal to this function's return value.
+// Params: width: the width of the new window
+// Returns: the distance from the left edge of the screen for the new window to
+//   be horizontally centered
+function GetCenteringLeft(win, width) {
+  return (win.screen.availWidth - width) >> 1;
+}
+
+// Used for vertically centering a new window of the given height in the
+// available screen. Set the new window's distance from the top of the screen
+// equal to this function's return value.
+// Params: height: the height of the new window
+// Returns: the distance from the top edge of the screen for the new window to
+//   be vertically aligned.
+function GetCenteringTop(win, height) {
+  return (win.screen.availHeight - height) >> 1;
+}
+
+/**
+ * Opens a child popup window that has no browser toolbar/decorations.
+ * (Copied from caribou's common.js library with small modifications.)
+ *
+ * @param url the URL for the new window (Note: this will be unique-ified)
+ * @param opt_name the name of the new window
+ * @param opt_width the width of the new window
+ * @param opt_height the height of the new window
+ * @param opt_center if true, the new window is centered in the available screen
+ * @param opt_hide_scrollbars if true, the window hides the scrollbars
+ * @param opt_noresize if true, makes window unresizable
+ * @param opt_blocked_msg message warning that the popup has been blocked
+ * @return {Window} a reference to the new child window
+ */
+function Popup(url, opt_name, opt_width, opt_height, opt_center,
+  opt_hide_scrollbars, opt_noresize, opt_blocked_msg) {
+  if (!opt_height) {
+    opt_height = Math.floor(GetWindowHeight(window.top) * 0.8);
+  }
+  if (!opt_width) {
+    opt_width = Math.min(GetAvailScreenWidth(window), opt_height);
+  }
+
+  let features = 'resizable=' + (opt_noresize ? 'no' : 'yes') + ',' +
+                 'scrollbars=' + (opt_hide_scrollbars ? 'no' : 'yes') + ',' +
+                 'width=' + opt_width + ',height=' + opt_height;
+  if (opt_center) {
+    features += ',left=' + GetCenteringLeft(window, opt_width) + ',' +
+                'top=' + GetCenteringTop(window, opt_height);
+  }
+  return OpenWindow(window, url, opt_name, features, opt_blocked_msg);
+}
+
+/**
+ * Opens a new window. Returns the new window handle. Tries to open the new
+ * window using top.open() first. If that doesn't work, then tries win.open().
+ * If that still doesn't work, prints an alert.
+ * (Copied from caribou's common.js library with small modifications.)
+ *
+ * @param win the parent window from which to open the new child window
+ * @param url the URL for the new window (Note: this will be unique-ified)
+ * @param opt_name the name of the new window
+ * @param opt_features the properties of the new window
+ * @param opt_blocked_msg message warning that the popup has been blocked
+ * @return {Window} a reference to the new child window
+ */
+function OpenWindow(win, url, opt_name, opt_features, opt_blocked_msg) {
+  let newwin = OpenWindowHelper(top, url, opt_name, opt_features);
+  if (!newwin || newwin.closed || !newwin.focus) {
+    newwin = OpenWindowHelper(win, url, opt_name, opt_features);
+  }
+  if (!newwin || newwin.closed || !newwin.focus) {
+    if (opt_blocked_msg) alert(opt_blocked_msg);
+  } else {
+    // Make sure that the window has the focus
+    newwin.focus();
+  }
+  return newwin;
+}
+
+/*
+ * Helper for OpenWindow().
+ * (Copied from caribou's common.js library with small modifications.)
+ */
+function OpenWindowHelper(win, url, name, features) {
+  let newwin;
+  if (features) {
+    newwin = win.open(url, name, features);
+  } else if (name) {
+    newwin = win.open(url, name);
+  } else {
+    newwin = win.open(url);
+  }
+  return newwin;
+}
+
+// ------------------------------------------------------------------------
+// String utilities
+// ------------------------------------------------------------------------
+// Do html escaping
+var amp_re_ = /&/g;
+var lt_re_ = /</g;
+var gt_re_ = />/g;
+
+// converts multiple ws chars to a single space, and strips
+// leading and trailing ws
+var spc_re_ = /\s+/g;
+var beg_spc_re_ = /^ /;
+var end_spc_re_ = / $/;
+
+var newline_re_ = /\r?\n/g;
+var spctab_re_ = /[ \t]+/g;
+var nbsp_re_ = /\xa0/g;
+
+// URL-decodes the string. We need to specially handle '+'s because
+// the javascript library doesn't properly convert them to spaces
+var plus_re_ = /\+/g;
+
+// Converts any instances of "\r" or "\r\n" style EOLs into "\n" (Line Feed),
+// and also trim the extra newlines and whitespaces at the end.
+var eol_re_ = /\r\n?/g;
+var trailingspc_re_ = /[\n\t ]+$/;
+
+// Converts a string to its canonicalized label form.
+var illegal_chars_re_ = /[ \/(){}&|\\\"\000]/g;
+
+// ------------------------------------------------------------------------
+// TextArea utilities
+// ------------------------------------------------------------------------
+
+// Gets the cursor pos in a text area. Returns -1 if the cursor pos cannot
+// be determined or if the cursor out of the textfield.
+function GetCursorPos(win, textfield) {
+  try {
+    if (IsDefined(textfield.selectionEnd)) {
+      // Mozilla directly supports this
+      return textfield.selectionEnd;
+    } else if (win.document.selection && win.document.selection.createRange) {
+      // IE doesn't export an accessor for the endpoints of a selection.
+      // Instead, it uses the TextRange object, which has an extremely obtuse
+      // API. Here's what seems to work:
+
+      // (1) Obtain a textfield from the current selection (cursor)
+      let tr = win.document.selection.createRange();
+
+      // Check if the current selection is in the textfield
+      if (tr.parentElement() != textfield) {
+        return -1;
+      }
+
+      // (2) Make a text range encompassing the textfield
+      let tr2 = tr.duplicate();
+      tr2.moveToElementText(textfield);
+
+      // (3) Move the end of the copy to the beginning of the selection
+      tr2.setEndPoint('EndToStart', tr);
+
+      // (4) The span of the textrange copy is equivalent to the cursor pos
+      let cursor = tr2.text.length;
+
+      // Finally, perform a sanity check to make sure the cursor is in the
+      // textfield. IE sometimes screws this up when the window is activated
+      if (cursor > textfield.value.length) {
+        return -1;
+      }
+      return cursor;
+    } else {
+      Debug('Unable to get cursor position for: ' + navigator.userAgent);
+
+      // Just return the size of the textfield
+      // TODO: Investigate how to get cursor pos in Safari!
+      return textfield.value.length;
+    }
+  } catch (e) {
+    DumpException(e, 'Cannot get cursor pos');
+  }
+
+  return -1;
+}
+
+function SetCursorPos(win, textfield, pos) {
+  if (IsDefined(textfield.selectionEnd) &&
+      IsDefined(textfield.selectionStart)) {
+    // Mozilla directly supports this
+    textfield.selectionStart = pos;
+    textfield.selectionEnd = pos;
+  } else if (win.document.selection && textfield.createTextRange) {
+    // IE has textranges. A textfield's textrange encompasses the
+    // entire textfield's text by default
+    let sel = textfield.createTextRange();
+
+    sel.collapse(true);
+    sel.move('character', pos);
+    sel.select();
+  }
+}
+
+// ------------------------------------------------------------------------
+// Array utilities
+// ------------------------------------------------------------------------
+// Find an item in an array, returns the key, or -1 if not found
+function FindInArray(array, x) {
+  for (let i = 0; i < array.length; i++) {
+    if (array[i] == x) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+// Delete an element from an array
+function DeleteArrayElement(array, x) {
+  let i = 0;
+  while (i < array.length && array[i] != x) {
+    i++;
+  }
+  array.splice(i, 1);
+}
+
+// Clean up email address:
+// - remove extra spaces
+// - Surround name with quotes if it contains special characters
+// to check if we need " quotes
+// Note: do not use /g in the regular expression, otherwise the
+// regular expression cannot be reusable.
+var specialchars_re_ = /[()<>@,;:\\\".\[\]]/;
+
+// ------------------------------------------------------------------------
+// Timeouts
+//
+// It is easy to forget to put a try/catch block around a timeout function,
+// and the result is an ugly user visible javascript error.
+// Also, it would be nice if a timeout associated with a window is
+// automatically cancelled when the user navigates away from that window.
+//
+// When storing timeouts in a window, we can't let that variable be renamed
+// since the window could be top.js, and renaming such a property could
+// clash with any of the variables/functions defined in top.js.
+// ------------------------------------------------------------------------
+/**
+ * Sets a timeout safely.
+ * @param win the window object. If null is passed in, then a timeout if set
+ *   on the js frame. If the window is closed, or freed, the timeout is
+ *   automaticaaly cancelled
+ * @param fn the callback function: fn(win) will be called.
+ * @param ms number of ms the callback should be called later
+ */
+function SafeTimeout(win, fn, ms) {
+  if (!win) win = window;
+  if (!win._tm) {
+    win._tm = [];
+  }
+  let timeoutfn = SafeTimeoutFunction_(win, fn);
+  let id = win.setTimeout(timeoutfn, ms);
+
+  // Save the id so that it can be removed from the _tm array
+  timeoutfn.id = id;
+
+  // Safe the timeout in the _tm array
+  win._tm[id] = 1;
+
+  return id;
+}
+
+/** Creates a callback function for a timeout*/
+function SafeTimeoutFunction_(win, fn) {
+  var timeoutfn = function() {
+    try {
+      fn(win);
+
+      let t = win._tm;
+      if (t) {
+        delete t[timeoutfn.id];
+      }
+    } catch (e) {
+      DumpException(e);
+    }
+  };
+  return timeoutfn;
+}
+
+// ------------------------------------------------------------------------
+// Misc
+// ------------------------------------------------------------------------
+// Check if a value is defined
+function IsDefined(value) {
+  return (typeof value) != 'undefined';
+}
diff --git a/static/js/graveyard/geom.js b/static/js/graveyard/geom.js
new file mode 100644
index 0000000..3eaffb7
--- /dev/null
+++ b/static/js/graveyard/geom.js
@@ -0,0 +1,94 @@
+/* Copyright 2016 The Chromium Authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+// functions for dealing with layout and geometry of page elements.
+// Requires shapes.js
+
+/** returns the bounding box of the given DOM node in document space.
+  *
+  * @param {Element?} obj a DOM node.
+  * @return {Rect?}
+  */
+function nodeBounds(obj) {
+  if (!obj) return null;
+
+  function fixRectForScrolling(r) {
+    // Need to take into account scrolling offset of ancestors (IE already does
+    // this)
+    for (let o = obj.offsetParent;
+      o && o.offsetParent;
+      o = o.offsetParent) {
+      if (o.scrollLeft) {
+        r.x -= o.scrollLeft;
+      }
+      if (o.scrollTop) {
+        r.y -= o.scrollTop;
+      }
+    }
+  }
+
+  let refWindow;
+  if (obj.ownerDocument && obj.ownerDocument.parentWindow) {
+    refWindow = obj.ownerDocument.parentWindow;
+  } else if (obj.ownerDocument && obj.ownerDocument.defaultView) {
+    refWindow = obj.ownerDocument.defaultView;
+  } else {
+    refWindow = window;
+  }
+
+  // IE, Mozilla 3+
+  if (obj.getBoundingClientRect) {
+    let rect = obj.getBoundingClientRect();
+
+    return new Rect(rect.left + GetScrollLeft(refWindow),
+      rect.top + GetScrollTop(refWindow),
+      rect.right - rect.left,
+      rect.bottom - rect.top,
+      refWindow);
+  }
+
+  // Mozilla < 3
+  if (obj.ownerDocument && obj.ownerDocument.getBoxObjectFor) {
+    let box = obj.ownerDocument.getBoxObjectFor(obj);
+    var r = new Rect(box.x, box.y, box.width, box.height, refWindow);
+    fixRectForScrolling(r);
+    return r;
+  }
+
+  // Fallback to recursively computing this
+  let left = 0;
+  let top = 0;
+  for (let o = obj; o.offsetParent; o = o.offsetParent) {
+    left += o.offsetLeft;
+    top += o.offsetTop;
+  }
+
+  var r = new Rect(left, top, obj.offsetWidth, obj.offsetHeight, refWindow);
+  fixRectForScrolling(r);
+  return r;
+}
+
+function GetMousePosition(e) {
+  // copied from http://www.quirksmode.org/js/events_compinfo.html
+  let posx = 0;
+  let posy = 0;
+  if (e.pageX || e.pageY) {
+    posx = e.pageX;
+    posy = e.pageY;
+  } else if (e.clientX || e.clientY) {
+    let obj = (e.target ? e.target : e.srcElement);
+    let refWindow;
+    if (obj.ownerDocument && obj.ownerDocument.parentWindow) {
+      refWindow = obj.ownerDocument.parentWindow;
+    } else {
+      refWindow = window;
+    }
+    posx = e.clientX + GetScrollLeft(refWindow);
+    posy = e.clientY + GetScrollTop(refWindow);
+  }
+  return new Point(posx, posy, window);
+}
diff --git a/static/js/graveyard/listen.js b/static/js/graveyard/listen.js
new file mode 100644
index 0000000..953d674
--- /dev/null
+++ b/static/js/graveyard/listen.js
@@ -0,0 +1,145 @@
+/* Copyright 2016 The Chromium Authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+var listen;
+var unlisten;
+var unlistenByKey;
+
+(function() {
+  let listeners = {};
+  let nextId = 0;
+
+  function getHashCode_(obj) {
+    if (obj.listen_hc_ == null) {
+      obj.listen_hc_ = ++nextId;
+    }
+    return obj.listen_hc_;
+  }
+
+  /**
+   * Takes a node, event, listener, and capture flag to create a key
+   * to identify the tuple in the listeners hash.
+   *
+   * @param {Element} node The node to listen to events on.
+   * @param {string} event The name of the event without the "on" prefix.
+   * @param {Function} listener A function to call when the event occurs.
+   * @param {boolean} opt_useCapture In DOM-compliant browsers, this determines
+   *                                 whether the listener is fired during the
+   *                                 capture or bubble phase of the event.
+   * @return {string} key to identify this tuple in the listeners hash.
+   */
+  function createKey_(node, event, listener, opt_useCapture) {
+    let nodeHc = getHashCode_(node);
+    let listenerHc = getHashCode_(listener);
+    opt_useCapture = !!opt_useCapture;
+    let key = nodeHc + '_' + event + '_' + listenerHc + '_' + opt_useCapture;
+    return key;
+  }
+
+  /**
+   * Adds an event listener to a DOM node for a specific event.
+   *
+   * Listen() and unlisten() use an indirect lookup of listener functions
+   * to avoid circular references between DOM (in IE) or XPCOM (in Mozilla)
+   * objects which leak memory. This makes it easier to write OO
+   * Javascript/DOM code.
+   *
+   * Examples:
+   * listen(myButton, 'click', myHandler, true);
+   * listen(myButton, 'click', this.myHandler.bind(this), true);
+   *
+   * @param {Element} node The node to listen to events on.
+   * @param {string} event The name of the event without the "on" prefix.
+   * @param {Function} listener A function to call when the event occurs.
+   * @param {boolean} opt_useCapture In DOM-compliant browsers, this determines
+   *                                 whether the listener is fired during the
+   *                                 capture or bubble phase of the event.
+   * @return {string} a unique key to indentify this listener.
+   */
+  listen = function(node, event, listener, opt_useCapture) {
+    let key = createKey_(node, event, listener, opt_useCapture);
+
+    // addEventListener does not allow multiple listeners
+    if (key in listeners) {
+      return key;
+    }
+
+    let proxy = handleEvent.bind(null, key);
+    listeners[key] = {
+      listener: listener,
+      proxy: proxy,
+      event: event,
+      node: node,
+      useCapture: opt_useCapture,
+    };
+
+    if (node.addEventListener) {
+      node.addEventListener(event, proxy, opt_useCapture);
+    } else if (node.attachEvent) {
+      node.attachEvent('on' + event, proxy);
+    } else {
+      throw new Error('Node {' + node + '} does not support event listeners.');
+    }
+
+    return key;
+  };
+
+  /**
+   * Removes an event listener which was added with listen().
+   *
+   * @param {Element} node The node to stop listening to events on.
+   * @param {string} event The name of the event without the "on" prefix.
+   * @param {Function} listener The listener function to remove.
+   * @param {boolean} opt_useCapture In DOM-compliant browsers, this determines
+   *                                 whether the listener is fired during the
+   *                                 capture or bubble phase of the event.
+   * @return {boolean} indicating whether the listener was there to remove.
+   */
+  unlisten = function(node, event, listener, opt_useCapture) {
+    let key = createKey_(node, event, listener, opt_useCapture);
+
+    return unlistenByKey(key);
+  };
+
+  /**
+   * Variant of {@link unlisten} that takes a key that was returned by
+   * {@link listen} and removes that listener.
+   *
+   * @param {string} key Key of event to be unlistened.
+   * @return {boolean} indicating whether it was there to be removed.
+   */
+  unlistenByKey = function(key) {
+    if (!(key in listeners)) {
+      return false;
+    }
+    let listener = listeners[key];
+    let proxy = listener.proxy;
+    let event = listener.event;
+    let node = listener.node;
+    let useCapture = listener.useCapture;
+
+    if (node.removeEventListener) {
+      node.removeEventListener(event, proxy, useCapture);
+    } else if (node.detachEvent) {
+      node.detachEvent('on' + event, proxy);
+    }
+
+    delete listeners[key];
+    return true;
+  };
+
+  /**
+   * The function which is actually called when the DOM event occurs. This
+   * function is a proxy for the real listener the user specified.
+   */
+  function handleEvent(key) {
+    // pass all arguments which were sent to this function except listenerID
+    // on to the actual listener.
+    let args = Array.prototype.splice.call(arguments, 1, arguments.length);
+    return listeners[key].listener.apply(null, args);
+  }
+})();
diff --git a/static/js/graveyard/popup_controller.js b/static/js/graveyard/popup_controller.js
new file mode 100644
index 0000000..41c2956
--- /dev/null
+++ b/static/js/graveyard/popup_controller.js
@@ -0,0 +1,145 @@
+/* Copyright 2016 The Chromium Authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+/**
+ * It is common to make a DIV temporarily visible to simulate
+ * a popup window. Often, this is done by adding an onClick
+ * handler to the element that can be clicked on to show the
+ * popup.
+ *
+ * Unfortunately, closing the popup is not as simple.
+ * The popup creator often wants to let the user close
+ * the popup by clicking elsewhere on the window; however,
+ * the popup only receives mouse events that occur
+ * on the popup itself. Thus, popups need a mechanism
+ * that notifies them that the user has clicked elsewhere
+ * to try to get rid of them.
+ *
+ * PopupController is such a mechanism --
+ * it monitors all mousedown events that
+ * occur in the window so that it can notify registered
+ * popups of the mousedown, and the popups can choose
+ * to deactivate themselves.
+ *
+ * For an object to qualify as a popup, it must have a
+ * function called "deactivate" that takes a mousedown event
+ * and returns a boolean indicating that it has deactivated
+ * itself as a result of that event.
+ *
+ * EXAMPLE:
+ *
+ * // popup that attaches itself to the supplied div
+ * function MyPopup(div) {
+ *   this._div = div;
+ *   this._isVisible = false;
+ *   this._innerHTML = ...
+ * }
+ *
+ * MyPopup.prototype.show = function() {
+ *   this._div.display = '';
+ *   this._isVisible = true;
+ *   PC_addPopup(this);
+ * }
+ *
+ * MyPopup.prototype.hide = function() {
+ *   this._div.display = 'none';
+ *   this._isVisible = false;
+ * }
+ *
+ * MyPopup.prototype.deactivate = function(e) {
+ *   if (this._isVisible) {
+ *     var p = GetMousePosition(e);
+ *     if (nodeBounds(this._div).contains(p)) {
+ *       return false; // use clicked on popup, remain visible
+ *     } else {
+ *       this.hide();
+ *       return true; // clicked outside popup, make invisible
+ *     }
+ *   } else {
+ *     return true; // already deactivated, not visible
+ *   }
+ * }
+ *
+ * DEPENDENCIES (from this directory):
+ *   bind.js
+ *   listen.js
+ *   common.js
+ *   shapes.js
+ *   geom.js
+ *
+ * USAGE:
+ *  _PC_Install() must be called after the body is loaded
+ */
+
+/**
+ * PopupController constructor.
+ * @constructor
+ */
+function PopupController() {
+  this.activePopups_ = [];
+}
+
+/**
+ * @param {Document} opt_doc document to add PopupController to
+ *                   DEFAULT: "document" variable that is currently in scope
+ * @return {boolean} indicating if PopupController installed for the document;
+ *                   returns false if document already had PopupController
+ */
+function _PC_Install(opt_doc) {
+  if (gPopupControllerInstalled) return false;
+  gPopupControllerInstalled = true;
+  let doc = (opt_doc) ? opt_doc : document;
+
+  // insert _notifyPopups in BODY's onmousedown chain
+  listen(doc.body, 'mousedown', PC_notifyPopups);
+  return true;
+}
+
+/**
+ * Notifies each popup of a mousedown event, giving
+ * each popup the chance to deactivate itself.
+ *
+ * @throws Error if a popup does not have a deactivate function
+ *
+ * @private
+ */
+function PC_notifyPopups(e) {
+  if (gPopupController.activePopups_.length == 0) return false;
+  e = e || window.event;
+  for (let i = gPopupController.activePopups_.length - 1; i >= 0; --i) {
+    let popup = gPopupController.activePopups_[i];
+    PC_assertIsPopup(popup);
+    if (popup.deactivate(e)) {
+      gPopupController.activePopups_.splice(i, 1);
+    }
+  }
+  return true;
+}
+
+/**
+ * Adds the popup to the list of popups to be
+ * notified of a mousedown event.
+ *
+ * @return boolean indicating if added popup; false if already contained
+ * @throws Error if popup does not have a deactivate function
+ */
+function PC_addPopup(popup) {
+  PC_assertIsPopup(popup);
+  for (let i = 0; i < gPopupController.activePopups_.length; ++i) {
+    if (popup === gPopupController.activePopups_[i]) return false;
+  }
+  gPopupController.activePopups_.push(popup);
+  return true;
+}
+
+/** asserts that popup has a deactivate function */
+function PC_assertIsPopup(popup) {
+  AssertType(popup.deactivate, Function, 'popup missing deactivate function');
+}
+
+var gPopupController = new PopupController();
+var gPopupControllerInstalled = false;
diff --git a/static/js/graveyard/shapes.js b/static/js/graveyard/shapes.js
new file mode 100644
index 0000000..27cd7f1
--- /dev/null
+++ b/static/js/graveyard/shapes.js
@@ -0,0 +1,126 @@
+/* Copyright 2016 The Chromium Authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+// shape related classes
+
+/** a point in 2 cartesian dimensions.
+  * @constructor
+  * @param x x-coord.
+  * @param y y-coord.
+  * @param opt_coordinateFrame a key that can be passed to a translation function to
+  *   convert from one coordinate frame to another.
+  *   Coordinate frames might correspond to things like windows, iframes, or
+  *   any element with a position style attribute.
+  */
+function Point(x, y, opt_coordinateFrame) {
+  /** a numeric x coordinate. */
+  this.x = x;
+  /** a numeric y coordinate. */
+  this.y = y;
+  /** a key that can be passed to a translation function to
+    * convert from one coordinate frame to another.
+    * Coordinate frames might correspond to things like windows, iframes, or
+    * any element with a position style attribute.
+    */
+  this.coordinateFrame = opt_coordinateFrame || null;
+}
+Point.prototype.toString = function() {
+  return '[P ' + this.x + ',' + this.y + ']';
+};
+Point.prototype.clone = function() {
+  return new Point(this.x, this.y, this.coordinateFrame);
+};
+
+/** a distance between two points in 2-space in cartesian form.
+  * A delta doesn't have a coordinate frame associated since all the coordinate
+  * frames used in the HTML dom are convertible without rotation/scaling.
+  * If a delta is not being used in pixel-space then it may be annotated with
+  * a coordinate frame, and the undefined coordinate frame can be assumed
+  * to represent pixel space.
+  * @constructor
+  * @param dx distance along x axis
+  * @param dy distance along y axis
+  */
+function Delta(dx, dy) {
+  /** a numeric distance along the x dimension. */
+  this.dx = dx;
+  /** a numeric distance along the y dimension. */
+  this.dy = dy;
+}
+Delta.prototype.toString = function() {
+  return '[D ' + this.dx + ',' + this.dy + ']';
+};
+
+/** a rectangle or bounding region.
+  * @constructor
+  * @param x x-coord of the left edge.
+  * @param y y-coord of the top edge.
+  * @param w width.
+  * @param h height.
+  * @param opt_coordinateFrame a key that can be passed to a translation function to
+  *   convert from one coordinate frame to another.
+  *   Coordinate frames might correspond to things like windows, iframes, or
+  *   any element with a position style attribute.
+  */
+function Rect(x, y, w, h, opt_coordinateFrame) {
+  /** the numeric x coordinate of the left edge. */
+  this.x = x;
+  /** the numeric y coordinate of the top edge. */
+  this.y = y;
+  /** the numeric distance between the right edge and the left. */
+  this.w = w;
+  /** the numeric distance between the top edge and the bottom. */
+  this.h = h;
+  /** a key that can be passed to a translation function to
+    * convert from one coordinate frame to another.
+    * Coordinate frames might correspond to things like windows, iframes, or
+    * any element with a position style attribute.
+    */
+  this.coordinateFrame = opt_coordinateFrame || null;
+}
+
+/**
+ * Determines whether the Rectangle contains the Point.
+ * The Point is considered "contained" if it lies
+ * on the boundary of, or in the interior of, the Rectangle.
+ *
+ * @param {Point} p
+ * @return boolean indicating if this Rect contains p
+ */
+Rect.prototype.contains = function(p) {
+  return this.x <= p.x && p.x < (this.x + this.w) &&
+             this.y <= p.y && p.y < (this.y + this.h);
+};
+
+/**
+ * Determines whether the given rectangle intersects this rectangle.
+ *
+ * @param {Rect} r
+ * @return boolean indicating if this the two rectangles intersect
+ */
+Rect.prototype.intersects = function(r) {
+  let p = function(x, y) {
+    return new Point(x, y, null);
+  };
+
+  return this.contains(p(r.x, r.y)) ||
+         this.contains(p(r.x + r.w, r.y)) ||
+         this.contains(p(r.x + r.w, r.y + r.h)) ||
+         this.contains(p(r.x, r.y + r.h)) ||
+         r.contains(p(this.x, this.y)) ||
+         r.contains(p(this.x + this.w, this.y)) ||
+         r.contains(p(this.x + this.w, this.y + this.h)) ||
+         r.contains(p(this.x, this.y + this.h));
+};
+
+Rect.prototype.toString = function() {
+  return '[R ' + this.w + 'x' + this.h + '+' + this.x + '+' + this.y + ']';
+};
+
+Rect.prototype.clone = function() {
+  return new Rect(this.x, this.y, this.w, this.h, this.coordinateFrame);
+};
diff --git a/static/js/graveyard/xmlhttp.js b/static/js/graveyard/xmlhttp.js
new file mode 100644
index 0000000..eaf1f36
--- /dev/null
+++ b/static/js/graveyard/xmlhttp.js
@@ -0,0 +1,141 @@
+/* Copyright 2016 The Chromium Authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+/**
+ * @fileoverview A bunch of XML HTTP recipes used to do RPC from JavaScript
+ */
+
+
+/**
+ * The active x identifier used for ie.
+ * @type String
+ * @private
+ */
+var XH_ieProgId_;
+
+
+// Domain for XMLHttpRequest readyState
+var XML_READY_STATE_UNINITIALIZED = 0;
+var XML_READY_STATE_LOADING = 1;
+var XML_READY_STATE_LOADED = 2;
+var XML_READY_STATE_INTERACTIVE = 3;
+var XML_READY_STATE_COMPLETED = 4;
+
+
+/**
+ * Initialize the private state used by other functions.
+ * @private
+ */
+function XH_XmlHttpInit_() {
+  // The following blog post describes what PROG IDs to use to create the
+  // XMLHTTP object in Internet Explorer:
+  // http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
+  // However we do not (yet) fully trust that this will be OK for old versions
+  // of IE on Win9x so we therefore keep the last 2.
+  // Versions 4 and 5 have been removed because 3.0 is the preferred "fallback"
+  // per the article above.
+  // - Version 5 was built for Office applications and is not recommended for
+  //   web applications.
+  // - Version 4 has been superseded by 6 and is only intended for legacy apps.
+  // - Version 3 has a wide install base and is serviced regularly with the OS.
+
+  /**
+   * Candidate Active X types.
+   * @type Array.<String>
+   * @private
+   */
+  let XH_ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0',
+    'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];
+
+  if (typeof XMLHttpRequest == 'undefined' &&
+      typeof ActiveXObject != 'undefined') {
+    for (let i = 0; i < XH_ACTIVE_X_IDENTS.length; i++) {
+      let candidate = XH_ACTIVE_X_IDENTS[i];
+
+      try {
+        new ActiveXObject(candidate);
+        XH_ieProgId_ = candidate;
+        break;
+      } catch (e) {
+        // do nothing; try next choice
+      }
+    }
+
+    // couldn't find any matches
+    if (!XH_ieProgId_) {
+      throw Error('Could not create ActiveXObject. ActiveX might be disabled,' +
+                  ' or MSXML might not be installed.');
+    }
+  }
+}
+
+
+XH_XmlHttpInit_();
+
+
+/**
+ * Create and return an xml http request object that can be passed to
+ * {@link #XH_XmlHttpGET} or {@link #XH_XmlHttpPOST}.
+ */
+function XH_XmlHttpCreate() {
+  if (XH_ieProgId_) {
+    return new ActiveXObject(XH_ieProgId_);
+  } else {
+    return new XMLHttpRequest();
+  }
+}
+
+
+/**
+ * Send a get request.
+ * @param {XMLHttpRequest} xmlHttp as from {@link XH_XmlHttpCreate}.
+ * @param {string} url the service to contact
+ * @param {Function} handler function called when the response is received.
+ */
+function XH_XmlHttpGET(xmlHttp, url, handler) {
+  xmlHttp.open('GET', url, true);
+  xmlHttp.onreadystatechange = handler;
+  XH_XmlHttpSend(xmlHttp, null);
+}
+
+/**
+ * Send a post request.
+ * @param {XMLHttpRequest} xmlHttp as from {@link XH_XmlHttpCreate}.
+ * @param {string} url the service to contact
+ * @param {string} data the request content.
+ * @param {Function} handler function called when the response is received.
+ */
+function XH_XmlHttpPOST(xmlHttp, url, data, handler) {
+  xmlHttp.open('POST', url, true);
+  xmlHttp.onreadystatechange = handler;
+  xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+  XH_XmlHttpSend(xmlHttp, data);
+}
+
+/**
+ * Calls 'send' on the XMLHttpRequest object and calls a function called 'log'
+ * if any error occured.
+ *
+ * @deprecated This dependes on a function called 'log'. You are better off
+ * handling your errors on application level.
+ *
+ * @param {XMLHttpRequest} xmlHttp as from {@link XH_XmlHttpCreate}.
+ * @param {string|null} data the request content.
+ */
+function XH_XmlHttpSend(xmlHttp, data) {
+  try {
+    xmlHttp.send(data);
+  } catch (e) {
+    // You may want to log/debug this error one that you should be aware of is
+    // e.number == -2146697208, which occurs when the 'Languages...' setting in
+    // IE is empty.
+    // This is not entirely true. The same error code is used when the user is
+    // off line.
+    console.log('XMLHttpSend failed ' + e.toString() + '<br>' + e.stack);
+    throw e;
+  }
+}