flex-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ftho...@apache.org
Subject [20/51] [abbrv] [partial] git commit: [flex-falcon] [refs/heads/JsToAs] - Added GCL extern.
Date Thu, 17 Sep 2015 15:28:35 GMT
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/range.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/range.js b/externs/GCL/externs/goog/editor/range.js
new file mode 100644
index 0000000..ec1a6a7
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/range.js
@@ -0,0 +1,632 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilties for working with ranges.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+goog.provide('goog.editor.range');
+goog.provide('goog.editor.range.Point');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.Range');
+goog.require('goog.dom.RangeEndpoint');
+goog.require('goog.dom.SavedCaretRange');
+goog.require('goog.editor.node');
+goog.require('goog.editor.style');
+goog.require('goog.iter');
+goog.require('goog.userAgent');
+
+
+/**
+ * Given a range and an element, create a narrower range that is limited to the
+ * boundaries of the element. If the range starts (or ends) outside the
+ * element, the narrowed range's start point (or end point) will be the
+ * leftmost (or rightmost) leaf of the element.
+ * @param {goog.dom.AbstractRange} range The range.
+ * @param {Element} el The element to limit the range to.
+ * @return {goog.dom.AbstractRange} A new narrowed range, or null if the
+ *     element does not contain any part of the given range.
+ */
+goog.editor.range.narrow = function(range, el) {
+  var startContainer = range.getStartNode();
+  var endContainer = range.getEndNode();
+
+  if (startContainer && endContainer) {
+    var isElement = function(node) {
+      return node == el;
+    };
+    var hasStart = goog.dom.getAncestor(startContainer, isElement, true);
+    var hasEnd = goog.dom.getAncestor(endContainer, isElement, true);
+
+    if (hasStart && hasEnd) {
+      // The range is contained entirely within this element.
+      return range.clone();
+    } else if (hasStart) {
+      // The range starts inside the element, but ends outside it.
+      var leaf = goog.editor.node.getRightMostLeaf(el);
+      return goog.dom.Range.createFromNodes(
+          range.getStartNode(), range.getStartOffset(),
+          leaf, goog.editor.node.getLength(leaf));
+    } else if (hasEnd) {
+      // The range starts outside the element, but ends inside it.
+      return goog.dom.Range.createFromNodes(
+          goog.editor.node.getLeftMostLeaf(el), 0,
+          range.getEndNode(), range.getEndOffset());
+    }
+  }
+
+  // The selection starts and ends outside the element.
+  return null;
+};
+
+
+/**
+ * Given a range, expand the range to include outer tags if the full contents of
+ * those tags are entirely selected.  This essentially changes the dom position,
+ * but not the visible position of the range.
+ * Ex. <li>foo</li> if "foo" is selected, instead of returning start and end
+ * nodes as the foo text node, return the li.
+ * @param {goog.dom.AbstractRange} range The range.
+ * @param {Node=} opt_stopNode Optional node to stop expanding past.
+ * @return {!goog.dom.AbstractRange} The expanded range.
+ */
+goog.editor.range.expand = function(range, opt_stopNode) {
+  // Expand the start out to the common container.
+  var expandedRange = goog.editor.range.expandEndPointToContainer_(
+      range, goog.dom.RangeEndpoint.START, opt_stopNode);
+  // Expand the end out to the common container.
+  expandedRange = goog.editor.range.expandEndPointToContainer_(
+      expandedRange, goog.dom.RangeEndpoint.END, opt_stopNode);
+
+  var startNode = expandedRange.getStartNode();
+  var endNode = expandedRange.getEndNode();
+  var startOffset = expandedRange.getStartOffset();
+  var endOffset = expandedRange.getEndOffset();
+
+  // If we have reached a common container, now expand out.
+  if (startNode == endNode) {
+    while (endNode != opt_stopNode &&
+           startOffset == 0 &&
+           endOffset == goog.editor.node.getLength(endNode)) {
+      // Select the parent instead.
+      var parentNode = endNode.parentNode;
+      startOffset = goog.array.indexOf(parentNode.childNodes, endNode);
+      endOffset = startOffset + 1;
+      endNode = parentNode;
+    }
+    startNode = endNode;
+  }
+
+  return goog.dom.Range.createFromNodes(startNode, startOffset,
+      endNode, endOffset);
+};
+
+
+/**
+ * Given a range, expands the start or end points as far out towards the
+ * range's common container (or stopNode, if provided) as possible, while
+ * perserving the same visible position.
+ *
+ * @param {goog.dom.AbstractRange} range The range to expand.
+ * @param {goog.dom.RangeEndpoint} endpoint The endpoint to expand.
+ * @param {Node=} opt_stopNode Optional node to stop expanding past.
+ * @return {!goog.dom.AbstractRange} The expanded range.
+ * @private
+ */
+goog.editor.range.expandEndPointToContainer_ = function(range, endpoint,
+                                                        opt_stopNode) {
+  var expandStart = endpoint == goog.dom.RangeEndpoint.START;
+  var node = expandStart ? range.getStartNode() : range.getEndNode();
+  var offset = expandStart ? range.getStartOffset() : range.getEndOffset();
+  var container = range.getContainerElement();
+
+  // Expand the node out until we reach the container or the stop node.
+  while (node != container && node != opt_stopNode) {
+    // It is only valid to expand the start if we are at the start of a node
+    // (offset 0) or expand the end if we are at the end of a node
+    // (offset length).
+    if (expandStart && offset != 0 ||
+        !expandStart && offset != goog.editor.node.getLength(node)) {
+      break;
+    }
+
+    var parentNode = node.parentNode;
+    var index = goog.array.indexOf(parentNode.childNodes, node);
+    offset = expandStart ? index : index + 1;
+    node = parentNode;
+  }
+
+  return goog.dom.Range.createFromNodes(
+      expandStart ? node : range.getStartNode(),
+      expandStart ? offset : range.getStartOffset(),
+      expandStart ? range.getEndNode() : node,
+      expandStart ? range.getEndOffset() : offset);
+};
+
+
+/**
+ * Cause the window's selection to be the start of this node.
+ * @param {Node} node The node to select the start of.
+ */
+goog.editor.range.selectNodeStart = function(node) {
+  goog.dom.Range.createCaret(goog.editor.node.getLeftMostLeaf(node), 0).
+      select();
+};
+
+
+/**
+ * Position the cursor immediately to the left or right of "node".
+ * In Firefox, the selection parent is outside of "node", so the cursor can
+ * effectively be moved to the end of a link node, without being considered
+ * inside of it.
+ * Note: This does not always work in WebKit. In particular, if you try to
+ * place a cursor to the right of a link, typing still puts you in the link.
+ * Bug: http://bugs.webkit.org/show_bug.cgi?id=17697
+ * @param {Node} node The node to position the cursor relative to.
+ * @param {boolean} toLeft True to place it to the left, false to the right.
+ * @return {!goog.dom.AbstractRange} The newly selected range.
+ */
+goog.editor.range.placeCursorNextTo = function(node, toLeft) {
+  var parent = node.parentNode;
+  var offset = goog.array.indexOf(parent.childNodes, node) +
+      (toLeft ? 0 : 1);
+  var point = goog.editor.range.Point.createDeepestPoint(
+      parent, offset, toLeft, true);
+  var range = goog.dom.Range.createCaret(point.node, point.offset);
+  range.select();
+  return range;
+};
+
+
+/**
+ * Normalizes the node, preserving the selection of the document.
+ *
+ * May also normalize things outside the node, if it is more efficient to do so.
+ *
+ * @param {Node} node The node to normalize.
+ */
+goog.editor.range.selectionPreservingNormalize = function(node) {
+  var doc = goog.dom.getOwnerDocument(node);
+  var selection = goog.dom.Range.createFromWindow(goog.dom.getWindow(doc));
+  var normalizedRange =
+      goog.editor.range.rangePreservingNormalize(node, selection);
+  if (normalizedRange) {
+    normalizedRange.select();
+  }
+};
+
+
+/**
+ * Manually normalizes the node in IE, since native normalize in IE causes
+ * transient problems.
+ * @param {Node} node The node to normalize.
+ * @private
+ */
+goog.editor.range.normalizeNodeIe_ = function(node) {
+  var lastText = null;
+  var child = node.firstChild;
+  while (child) {
+    var next = child.nextSibling;
+    if (child.nodeType == goog.dom.NodeType.TEXT) {
+      if (child.nodeValue == '') {
+        node.removeChild(child);
+      } else if (lastText) {
+        lastText.nodeValue += child.nodeValue;
+        node.removeChild(child);
+      } else {
+        lastText = child;
+      }
+    } else {
+      goog.editor.range.normalizeNodeIe_(child);
+      lastText = null;
+    }
+    child = next;
+  }
+};
+
+
+/**
+ * Normalizes the given node.
+ * @param {Node} node The node to normalize.
+ */
+goog.editor.range.normalizeNode = function(node) {
+  if (goog.userAgent.IE) {
+    goog.editor.range.normalizeNodeIe_(node);
+  } else {
+    node.normalize();
+  }
+};
+
+
+/**
+ * Normalizes the node, preserving a range of the document.
+ *
+ * May also normalize things outside the node, if it is more efficient to do so.
+ *
+ * @param {Node} node The node to normalize.
+ * @param {goog.dom.AbstractRange?} range The range to normalize.
+ * @return {goog.dom.AbstractRange?} The range, adjusted for normalization.
+ */
+goog.editor.range.rangePreservingNormalize = function(node, range) {
+  if (range) {
+    var rangeFactory = goog.editor.range.normalize(range);
+    // WebKit has broken selection affinity, so carets tend to jump out of the
+    // beginning of inline elements. This means that if we're doing the
+    // normalize as the result of a range that will later become the selection,
+    // we might not normalize something in the range after it is read back from
+    // the selection. We can't just normalize the parentNode here because WebKit
+    // can move the selection range out of multiple inline parents.
+    var container = goog.editor.style.getContainer(range.getContainerElement());
+  }
+
+  if (container) {
+    goog.editor.range.normalizeNode(
+        goog.dom.findCommonAncestor(container, node));
+  } else if (node) {
+    goog.editor.range.normalizeNode(node);
+  }
+
+  if (rangeFactory) {
+    return rangeFactory();
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * Get the deepest point in the DOM that's equivalent to the endpoint of the
+ * given range.
+ *
+ * @param {goog.dom.AbstractRange} range A range.
+ * @param {boolean} atStart True for the start point, false for the end point.
+ * @return {!goog.editor.range.Point} The end point, expressed as a node
+ *    and an offset.
+ */
+goog.editor.range.getDeepEndPoint = function(range, atStart) {
+  return atStart ?
+      goog.editor.range.Point.createDeepestPoint(
+          range.getStartNode(), range.getStartOffset()) :
+      goog.editor.range.Point.createDeepestPoint(
+          range.getEndNode(), range.getEndOffset());
+};
+
+
+/**
+ * Given a range in the current DOM, create a factory for a range that
+ * represents the same selection in a normalized DOM. The factory function
+ * should be invoked after the DOM is normalized.
+ *
+ * All browsers do a bad job preserving ranges across DOM normalization.
+ * The issue is best described in this 5-year-old bug report:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=191864
+ * For most applications, this isn't a problem. The browsers do a good job
+ * handling un-normalized text, so there's usually no reason to normalize.
+ *
+ * The exception to this rule is the rich text editing commands
+ * execCommand and queryCommandValue, which will fail often if there are
+ * un-normalized text nodes.
+ *
+ * The factory function creates new ranges so that we can normalize the DOM
+ * without problems. It must be created before any normalization happens,
+ * and invoked after normalization happens.
+ *
+ * @param {goog.dom.AbstractRange} range The range to normalize. It may
+ *    become invalid after body.normalize() is called.
+ * @return {function(): goog.dom.AbstractRange} A factory for a normalized
+ *    range. Should be called after body.normalize() is called.
+ */
+goog.editor.range.normalize = function(range) {
+  var isReversed = range.isReversed();
+  var anchorPoint = goog.editor.range.normalizePoint_(
+      goog.editor.range.getDeepEndPoint(range, !isReversed));
+  var anchorParent = anchorPoint.getParentPoint();
+  var anchorPreviousSibling = anchorPoint.node.previousSibling;
+  if (anchorPoint.node.nodeType == goog.dom.NodeType.TEXT) {
+    anchorPoint.node = null;
+  }
+
+  var focusPoint = goog.editor.range.normalizePoint_(
+      goog.editor.range.getDeepEndPoint(range, isReversed));
+  var focusParent = focusPoint.getParentPoint();
+  var focusPreviousSibling = focusPoint.node.previousSibling;
+  if (focusPoint.node.nodeType == goog.dom.NodeType.TEXT) {
+    focusPoint.node = null;
+  }
+
+  return function() {
+    if (!anchorPoint.node && anchorPreviousSibling) {
+      // If anchorPoint.node was previously an empty text node with no siblings,
+      // anchorPreviousSibling may not have a nextSibling since that node will
+      // no longer exist.  Do our best and point to the end of the previous
+      // element.
+      anchorPoint.node = anchorPreviousSibling.nextSibling;
+      if (!anchorPoint.node) {
+        anchorPoint = goog.editor.range.Point.getPointAtEndOfNode(
+            anchorPreviousSibling);
+      }
+    }
+
+    if (!focusPoint.node && focusPreviousSibling) {
+      // If focusPoint.node was previously an empty text node with no siblings,
+      // focusPreviousSibling may not have a nextSibling since that node will no
+      // longer exist.  Do our best and point to the end of the previous
+      // element.
+      focusPoint.node = focusPreviousSibling.nextSibling;
+      if (!focusPoint.node) {
+        focusPoint = goog.editor.range.Point.getPointAtEndOfNode(
+            focusPreviousSibling);
+      }
+    }
+
+    return goog.dom.Range.createFromNodes(
+        anchorPoint.node || anchorParent.node.firstChild || anchorParent.node,
+        anchorPoint.offset,
+        focusPoint.node || focusParent.node.firstChild || focusParent.node,
+        focusPoint.offset);
+  };
+};
+
+
+/**
+ * Given a point in the current DOM, adjust it to represent the same point in
+ * a normalized DOM.
+ *
+ * See the comments on goog.editor.range.normalize for more context.
+ *
+ * @param {goog.editor.range.Point} point A point in the document.
+ * @return {!goog.editor.range.Point} The same point, for easy chaining.
+ * @private
+ */
+goog.editor.range.normalizePoint_ = function(point) {
+  var previous;
+  if (point.node.nodeType == goog.dom.NodeType.TEXT) {
+    // If the cursor position is in a text node,
+    // look at all the previous text siblings of the text node,
+    // and set the offset relative to the earliest text sibling.
+    for (var current = point.node.previousSibling;
+         current && current.nodeType == goog.dom.NodeType.TEXT;
+         current = current.previousSibling) {
+      point.offset += goog.editor.node.getLength(current);
+    }
+
+    previous = current;
+  } else {
+    previous = point.node.previousSibling;
+  }
+
+  var parent = point.node.parentNode;
+  point.node = previous ? previous.nextSibling : parent.firstChild;
+  return point;
+};
+
+
+/**
+ * Checks if a range is completely inside an editable region.
+ * @param {goog.dom.AbstractRange} range The range to test.
+ * @return {boolean} Whether the range is completely inside an editable region.
+ */
+goog.editor.range.isEditable = function(range) {
+  var rangeContainer = range.getContainerElement();
+
+  // Closure's implementation of getContainerElement() is a little too
+  // smart in IE when exactly one element is contained in the range.
+  // It assumes that there's a user whose intent was actually to select
+  // all that element's children, so it returns the element itself as its
+  // own containing element.
+  // This little sanity check detects this condition so we can account for it.
+  var rangeContainerIsOutsideRange =
+      range.getStartNode() != rangeContainer.parentElement;
+
+  return (rangeContainerIsOutsideRange &&
+          goog.editor.node.isEditableContainer(rangeContainer)) ||
+      goog.editor.node.isEditable(rangeContainer);
+};
+
+
+/**
+ * Returns whether the given range intersects with any instance of the given
+ * tag.
+ * @param {goog.dom.AbstractRange} range The range to check.
+ * @param {goog.dom.TagName} tagName The name of the tag.
+ * @return {boolean} Whether the given range intersects with any instance of
+ *     the given tag.
+ */
+goog.editor.range.intersectsTag = function(range, tagName) {
+  if (goog.dom.getAncestorByTagNameAndClass(range.getContainerElement(),
+                                            tagName)) {
+    return true;
+  }
+
+  return goog.iter.some(range, function(node) {
+    return node.tagName == tagName;
+  });
+};
+
+
+
+/**
+ * One endpoint of a range, represented as a Node and and offset.
+ * @param {Node} node The node containing the point.
+ * @param {number} offset The offset of the point into the node.
+ * @constructor
+ * @final
+ */
+goog.editor.range.Point = function(node, offset) {
+  /**
+   * The node containing the point.
+   * @type {Node}
+   */
+  this.node = node;
+
+  /**
+   * The offset of the point into the node.
+   * @type {number}
+   */
+  this.offset = offset;
+};
+
+
+/**
+ * Gets the point of this point's node in the DOM.
+ * @return {!goog.editor.range.Point} The node's point.
+ */
+goog.editor.range.Point.prototype.getParentPoint = function() {
+  var parent = this.node.parentNode;
+  return new goog.editor.range.Point(
+      parent, goog.array.indexOf(parent.childNodes, this.node));
+};
+
+
+/**
+ * Construct the deepest possible point in the DOM that's equivalent
+ * to the given point, expressed as a node and an offset.
+ * @param {Node} node The node containing the point.
+ * @param {number} offset The offset of the point from the node.
+ * @param {boolean=} opt_trendLeft Notice that a (node, offset) pair may be
+ *     equivalent to more than one descendent (node, offset) pair in the DOM.
+ *     By default, we trend rightward. If this parameter is true, then we
+ *     trend leftward. The tendency to fall rightward by default is for
+ *     consistency with other range APIs (like placeCursorNextTo).
+ * @param {boolean=} opt_stopOnChildlessElement If true, and we encounter
+ *     a Node which is an Element that cannot have children, we return a Point
+ *     based on its parent rather than that Node itself.
+ * @return {!goog.editor.range.Point} A new point.
+ */
+goog.editor.range.Point.createDeepestPoint =
+    function(node, offset, opt_trendLeft, opt_stopOnChildlessElement) {
+  while (node.nodeType == goog.dom.NodeType.ELEMENT) {
+    var child = node.childNodes[offset];
+    if (!child && !node.lastChild) {
+      break;
+    } else if (child) {
+      var prevSibling = child.previousSibling;
+      if (opt_trendLeft && prevSibling) {
+        if (opt_stopOnChildlessElement &&
+            goog.editor.range.Point.isTerminalElement_(prevSibling)) {
+          break;
+        }
+        node = prevSibling;
+        offset = goog.editor.node.getLength(node);
+      } else {
+        if (opt_stopOnChildlessElement &&
+            goog.editor.range.Point.isTerminalElement_(child)) {
+          break;
+        }
+        node = child;
+        offset = 0;
+      }
+    } else {
+      if (opt_stopOnChildlessElement &&
+          goog.editor.range.Point.isTerminalElement_(node.lastChild)) {
+        break;
+      }
+      node = node.lastChild;
+      offset = goog.editor.node.getLength(node);
+    }
+  }
+
+  return new goog.editor.range.Point(node, offset);
+};
+
+
+/**
+ * Return true if the specified node is an Element that is not expected to have
+ * children. The createDeepestPoint() method should not traverse into
+ * such elements.
+ * @param {Node} node .
+ * @return {boolean} True if the node is an Element that does not contain
+ *     child nodes (e.g. BR, IMG).
+ * @private
+ */
+goog.editor.range.Point.isTerminalElement_ = function(node) {
+  return (node.nodeType == goog.dom.NodeType.ELEMENT &&
+          !goog.dom.canHaveChildren(node));
+};
+
+
+/**
+ * Construct a point at the very end of the given node.
+ * @param {Node} node The node to create a point for.
+ * @return {!goog.editor.range.Point} A new point.
+ */
+goog.editor.range.Point.getPointAtEndOfNode = function(node) {
+  return new goog.editor.range.Point(node, goog.editor.node.getLength(node));
+};
+
+
+/**
+ * Saves the range by inserting carets into the HTML.
+ *
+ * Unlike the regular saveUsingCarets, this SavedRange normalizes text nodes.
+ * Browsers have other bugs where they don't handle split text nodes in
+ * contentEditable regions right.
+ *
+ * @param {goog.dom.AbstractRange} range The abstract range object.
+ * @return {!goog.dom.SavedCaretRange} A saved caret range that normalizes
+ *     text nodes.
+ */
+goog.editor.range.saveUsingNormalizedCarets = function(range) {
+  return new goog.editor.range.NormalizedCaretRange_(range);
+};
+
+
+
+/**
+ * Saves the range using carets, but normalizes text nodes when carets
+ * are removed.
+ * @see goog.editor.range.saveUsingNormalizedCarets
+ * @param {goog.dom.AbstractRange} range The range being saved.
+ * @constructor
+ * @extends {goog.dom.SavedCaretRange}
+ * @private
+ */
+goog.editor.range.NormalizedCaretRange_ = function(range) {
+  goog.dom.SavedCaretRange.call(this, range);
+};
+goog.inherits(goog.editor.range.NormalizedCaretRange_,
+    goog.dom.SavedCaretRange);
+
+
+/**
+ * Normalizes text nodes whenever carets are removed from the document.
+ * @param {goog.dom.AbstractRange=} opt_range A range whose offsets have already
+ *     been adjusted for caret removal; it will be adjusted and returned if it
+ *     is also affected by post-removal operations, such as text node
+ *     normalization.
+ * @return {goog.dom.AbstractRange|undefined} The adjusted range, if opt_range
+ *     was provided.
+ * @override
+ */
+goog.editor.range.NormalizedCaretRange_.prototype.removeCarets =
+    function(opt_range) {
+  var startCaret = this.getCaret(true);
+  var endCaret = this.getCaret(false);
+  var node = startCaret && endCaret ?
+      goog.dom.findCommonAncestor(startCaret, endCaret) :
+      startCaret || endCaret;
+
+  goog.editor.range.NormalizedCaretRange_.superClass_.removeCarets.call(this);
+
+  if (opt_range) {
+    return goog.editor.range.rangePreservingNormalize(node, opt_range);
+  } else if (node) {
+    goog.editor.range.selectionPreservingNormalize(node);
+  }
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/seamlessfield.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/seamlessfield.js b/externs/GCL/externs/goog/editor/seamlessfield.js
new file mode 100644
index 0000000..7d84533
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/seamlessfield.js
@@ -0,0 +1,746 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Class to encapsulate an editable field that blends in with
+ * the style of the page. The field can be fixed height, grow with its
+ * contents, or have a min height after which it grows to its contents.
+ * This is a goog.editor.Field, but with blending and sizing capabilities,
+ * and avoids using an iframe whenever possible.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ * @see ../demos/editor/seamlessfield.html
+ */
+
+
+goog.provide('goog.editor.SeamlessField');
+
+goog.require('goog.cssom.iframe.style');
+goog.require('goog.dom');
+goog.require('goog.dom.Range');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.safe');
+goog.require('goog.editor.BrowserFeature');
+goog.require('goog.editor.Field');
+goog.require('goog.editor.icontent');
+goog.require('goog.editor.icontent.FieldFormatInfo');
+goog.require('goog.editor.icontent.FieldStyleInfo');
+goog.require('goog.editor.node');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.html.uncheckedconversions');
+goog.require('goog.log');
+goog.require('goog.string.Const');
+goog.require('goog.style');
+
+
+
+/**
+ * This class encapsulates an editable field that blends in with the
+ * surrounding page.
+ * To see events fired by this object, please see the base class.
+ *
+ * @param {string} id An identifer for the field. This is used to find the
+ *     field and the element associated with this field.
+ * @param {Document=} opt_doc The document that the element with the given
+ *     id can be found it.
+ * @constructor
+ * @extends {goog.editor.Field}
+ */
+goog.editor.SeamlessField = function(id, opt_doc) {
+  goog.editor.Field.call(this, id, opt_doc);
+};
+goog.inherits(goog.editor.SeamlessField, goog.editor.Field);
+
+
+/**
+ * @override
+ */
+goog.editor.SeamlessField.prototype.logger =
+    goog.log.getLogger('goog.editor.SeamlessField');
+
+// Functions dealing with field sizing.
+
+
+/**
+ * The key used for listening for the "dragover" event.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.listenForDragOverEventKey_;
+
+
+/**
+ * The key used for listening for the iframe "load" event.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.listenForIframeLoadEventKey_;
+
+
+/**
+ * Sets the min height of this editable field's iframe. Only used in growing
+ * mode when an iframe is used. This will cause an immediate field sizing to
+ * update the field if necessary based on the new min height.
+ * @param {number} height The min height specified as a number of pixels,
+ *    e.g., 75.
+ */
+goog.editor.SeamlessField.prototype.setMinHeight = function(height) {
+  if (height == this.minHeight_) {
+    // Do nothing if the min height isn't changing.
+    return;
+  }
+  this.minHeight_ = height;
+  if (this.usesIframe()) {
+    this.doFieldSizingGecko();
+  }
+};
+
+
+/**
+ * Whether the field should be rendered with a fixed height, or should expand
+ * to fit its contents.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.isFixedHeight_ = false;
+
+
+/**
+ * Whether the fixed-height handling has been overridden manually.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.isFixedHeightOverridden_ = false;
+
+
+/**
+ * @return {boolean} Whether the field should be rendered with a fixed
+ *    height, or should expand to fit its contents.
+ * @override
+ */
+goog.editor.SeamlessField.prototype.isFixedHeight = function() {
+  return this.isFixedHeight_;
+};
+
+
+/**
+ * @param {boolean} newVal Explicitly set whether the field should be
+ *    of a fixed-height. This overrides auto-detection.
+ */
+goog.editor.SeamlessField.prototype.overrideFixedHeight = function(newVal) {
+  this.isFixedHeight_ = newVal;
+  this.isFixedHeightOverridden_ = true;
+};
+
+
+/**
+ * Auto-detect whether the current field should have a fixed height.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.autoDetectFixedHeight_ = function() {
+  if (!this.isFixedHeightOverridden_) {
+    var originalElement = this.getOriginalElement();
+    if (originalElement) {
+      this.isFixedHeight_ =
+          goog.style.getComputedOverflowY(originalElement) == 'auto';
+    }
+  }
+};
+
+
+/**
+ * Resize the iframe in response to the wrapper div changing size.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.handleOuterDocChange_ = function() {
+  if (this.isEventStopped(goog.editor.Field.EventType.CHANGE)) {
+    return;
+  }
+  this.sizeIframeToWrapperGecko_();
+};
+
+
+/**
+ * Sizes the iframe to its body's height.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.sizeIframeToBodyHeightGecko_ = function() {
+  if (this.acquireSizeIframeLockGecko_()) {
+    var resized = false;
+    var ifr = this.getEditableIframe();
+    if (ifr) {
+      var fieldHeight = this.getIframeBodyHeightGecko_();
+
+      if (this.minHeight_) {
+        fieldHeight = Math.max(fieldHeight, this.minHeight_);
+      }
+      if (parseInt(goog.style.getStyle(ifr, 'height'), 10) != fieldHeight) {
+        ifr.style.height = fieldHeight + 'px';
+        resized = true;
+      }
+    }
+    this.releaseSizeIframeLockGecko_();
+    if (resized) {
+      this.dispatchEvent(goog.editor.Field.EventType.IFRAME_RESIZED);
+    }
+  }
+};
+
+
+/**
+ * @return {number} The height of the editable iframe's body.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.getIframeBodyHeightGecko_ = function() {
+  var ifr = this.getEditableIframe();
+  var body = ifr.contentDocument.body;
+  var htmlElement = body.parentNode;
+
+
+  // If the iframe's height is 0, then the offsetHeight/scrollHeight of the
+  // HTML element in the iframe can be totally wack (i.e. too large
+  // by 50-500px). Also, in standard's mode the clientHeight is 0.
+  if (parseInt(goog.style.getStyle(ifr, 'height'), 10) === 0) {
+    goog.style.setStyle(ifr, 'height', 1 + 'px');
+  }
+
+  var fieldHeight;
+  if (goog.editor.node.isStandardsMode(body)) {
+
+    // If in standards-mode,
+    // grab the HTML element as it will contain all the field's
+    // contents. The body's height, for example, will not include that of
+    // floated images at the bottom in standards mode.
+    // Note that this value include all scrollbars *except* for scrollbars
+    // on the HTML element itself.
+    fieldHeight = htmlElement.offsetHeight;
+  } else {
+    // In quirks-mode, the body-element always seems
+    // to size to the containing window.  The html-element however,
+    // sizes to the content, and can thus end up with a value smaller
+    // than its child body-element if the content is shrinking.
+    // We want to make the iframe shrink too when the content shrinks,
+    // so rather than size the iframe to the body-element, size it to
+    // the html-element.
+    fieldHeight = htmlElement.scrollHeight;
+
+    // If there is a horizontal scroll, add in the thickness of the
+    // scrollbar.
+    if (htmlElement.clientHeight != htmlElement.offsetHeight) {
+      fieldHeight += goog.editor.SeamlessField.getScrollbarWidth_();
+    }
+  }
+
+  return fieldHeight;
+};
+
+
+/**
+ * Grabs the width of a scrollbar from the browser and caches the result.
+ * @return {number} The scrollbar width in pixels.
+ * @private
+ */
+goog.editor.SeamlessField.getScrollbarWidth_ = function() {
+  return goog.editor.SeamlessField.scrollbarWidth_ ||
+      (goog.editor.SeamlessField.scrollbarWidth_ =
+          goog.style.getScrollbarWidth());
+};
+
+
+/**
+ * Sizes the iframe to its container div's width. The width of the div
+ * is controlled by its containing context, not by its contents.
+ * if it extends outside of it's contents, then it gets a horizontal scroll.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.sizeIframeToWrapperGecko_ = function() {
+  if (this.acquireSizeIframeLockGecko_()) {
+    var ifr = this.getEditableIframe();
+    var field = this.getElement();
+    var resized = false;
+    if (ifr && field) {
+      var fieldPaddingBox;
+      var widthDiv = ifr.parentNode;
+
+      var width = widthDiv.offsetWidth;
+      if (parseInt(goog.style.getStyle(ifr, 'width'), 10) != width) {
+        fieldPaddingBox = goog.style.getPaddingBox(field);
+        ifr.style.width = width + 'px';
+        field.style.width =
+            width - fieldPaddingBox.left - fieldPaddingBox.right + 'px';
+        resized = true;
+      }
+
+      var height = widthDiv.offsetHeight;
+      if (this.isFixedHeight() &&
+          parseInt(goog.style.getStyle(ifr, 'height'), 10) != height) {
+        if (!fieldPaddingBox) {
+          fieldPaddingBox = goog.style.getPaddingBox(field);
+        }
+        ifr.style.height = height + 'px';
+        field.style.height =
+            height - fieldPaddingBox.top - fieldPaddingBox.bottom + 'px';
+        resized = true;
+      }
+
+    }
+    this.releaseSizeIframeLockGecko_();
+    if (resized) {
+      this.dispatchEvent(goog.editor.Field.EventType.IFRAME_RESIZED);
+    }
+  }
+};
+
+
+/**
+ * Perform all the sizing immediately.
+ */
+goog.editor.SeamlessField.prototype.doFieldSizingGecko = function() {
+  // Because doFieldSizingGecko can be called after a setTimeout
+  // it is possible that the field has been destroyed before this call
+  // to do the sizing is executed. Check for field existence and do nothing
+  // if it has already been destroyed.
+  if (this.getElement()) {
+    // The order of operations is important here.  Sizing the iframe to the
+    // wrapper could cause the width to change, which could change the line
+    // wrapping, which could change the body height.  So we need to do that
+    // first, then size the iframe to fit the body height.
+    this.sizeIframeToWrapperGecko_();
+    if (!this.isFixedHeight()) {
+      this.sizeIframeToBodyHeightGecko_();
+    }
+  }
+};
+
+
+/**
+ * Acquires a lock on resizing the field iframe. This is used to ensure that
+ * modifications we make while in a mutation event handler don't cause
+ * infinite loops.
+ * @return {boolean} False if the lock is already acquired.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.acquireSizeIframeLockGecko_ = function() {
+  if (this.sizeIframeLock_) {
+    return false;
+  }
+  return this.sizeIframeLock_ = true;
+};
+
+
+/**
+ * Releases a lock on resizing the field iframe. This is used to ensure that
+ * modifications we make while in a mutation event handler don't cause
+ * infinite loops.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.releaseSizeIframeLockGecko_ = function() {
+  this.sizeIframeLock_ = false;
+};
+
+
+// Functions dealing with blending in with the surrounding page.
+
+
+/**
+ * String containing the css rules that, if applied to a document's body,
+ * would style that body as if it were the original element we made editable.
+ * See goog.cssom.iframe.style.getElementContext for more details.
+ * @type {string}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.iframeableCss_ = '';
+
+
+/**
+ * Gets the css rules that should be used to style an iframe's body as if it
+ * were the original element that we made editable.
+ * @param {boolean=} opt_forceRegeneration Set to true to not read the cached
+ * copy and instead completely regenerate the css rules.
+ * @return {string} The string containing the css rules to use.
+ */
+goog.editor.SeamlessField.prototype.getIframeableCss = function(
+    opt_forceRegeneration) {
+  if (!this.iframeableCss_ || opt_forceRegeneration) {
+    var originalElement = this.getOriginalElement();
+    if (originalElement) {
+      this.iframeableCss_ =
+          goog.cssom.iframe.style.getElementContext(originalElement,
+          opt_forceRegeneration);
+    }
+  }
+  return this.iframeableCss_;
+};
+
+
+/**
+ * Sets the css rules that should be used inside the editable iframe.
+ * Note: to clear the css cache between makeNotEditable/makeEditable,
+ * call this with "" as iframeableCss.
+ * TODO(user): Unify all these css setting methods + Nick's open
+ * CL.  This is getting ridiculous.
+ * @param {string} iframeableCss String containing the css rules to use.
+ */
+goog.editor.SeamlessField.prototype.setIframeableCss = function(iframeableCss) {
+  this.iframeableCss_ = iframeableCss;
+};
+
+
+/**
+ * Used to ensure that CSS stylings are only installed once for none
+ * iframe seamless mode.
+ * TODO(user): Make it a formal part of the API that you can only
+ * set one set of styles globally.
+ * In seamless, non-iframe mode, all the stylings would go in the
+ * same document and conflict.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.SeamlessField.haveInstalledCss_ = false;
+
+
+/**
+ * Applies CSS from the wrapper-div to the field iframe.
+ */
+goog.editor.SeamlessField.prototype.inheritBlendedCSS = function() {
+  // No-op if the field isn't using an iframe.
+  if (!this.usesIframe()) {
+    return;
+  }
+  var field = this.getElement();
+  var head = goog.dom.getDomHelper(field).getElementsByTagNameAndClass(
+      goog.dom.TagName.HEAD)[0];
+  if (head) {
+    // We created this <head>, and we know the only thing we put in there
+    // is a <style> block.  So it's safe to blow away all the children
+    // as part of rewriting the styles.
+    goog.dom.removeChildren(head);
+  }
+
+  // Force a cache-clearing in CssUtil - this function was called because
+  // we're applying the 'blend' for the first time, or because we
+  // *need* to recompute the blend.
+  var newCSS = this.getIframeableCss(true);
+  goog.style.installStyles(newCSS, field);
+};
+
+
+// Overridden methods.
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.usesIframe = function() {
+  // TODO(user): Switch Firefox to using contentEditable
+  // rather than designMode iframe once contentEditable support
+  // is less buggy.
+  return !goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE;
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.setupMutationEventHandlersGecko =
+    function() {
+  goog.editor.SeamlessField.superClass_.setupMutationEventHandlersGecko.call(
+      this);
+
+  if (this.usesIframe()) {
+    var iframe = this.getEditableIframe();
+    var outerDoc = iframe.ownerDocument;
+    this.eventRegister.listen(outerDoc,
+        goog.editor.Field.MUTATION_EVENTS_GECKO,
+        this.handleOuterDocChange_, true);
+
+    // If the images load after we do the initial sizing, then this will
+    // force a field resize.
+    this.listenForIframeLoadEventKey_ = goog.events.listenOnce(
+        this.getEditableDomHelper().getWindow(),
+        goog.events.EventType.LOAD, this.sizeIframeToBodyHeightGecko_,
+        true, this);
+
+    this.eventRegister.listen(outerDoc,
+        'DOMAttrModified',
+        goog.bind(this.handleDomAttrChange, this, this.handleOuterDocChange_),
+        true);
+  }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.handleChange = function() {
+  if (this.isEventStopped(goog.editor.Field.EventType.CHANGE)) {
+    return;
+  }
+
+  goog.editor.SeamlessField.superClass_.handleChange.call(this);
+
+  if (this.usesIframe()) {
+    this.sizeIframeToBodyHeightGecko_();
+  }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.dispatchBlur = function() {
+  if (this.isEventStopped(goog.editor.Field.EventType.BLUR)) {
+    return;
+  }
+
+  goog.editor.SeamlessField.superClass_.dispatchBlur.call(this);
+
+  // Clear the selection and restore the current range back after collapsing
+  // it. The ideal solution would have been to just leave the range intact; but
+  // when there are multiple fields present on the page, its important that
+  // the selection isn't retained when we switch between the fields. We also
+  // have to make sure that the cursor position is retained when we tab in and
+  // out of a field and our approach addresses both these issues.
+  // Another point to note is that we do it on a setTimeout to allow for
+  // DOM modifications on blur. Otherwise, something like setLoremIpsum will
+  // leave a blinking cursor in the field even though it's blurred.
+  if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE &&
+      !goog.editor.BrowserFeature.CLEARS_SELECTION_WHEN_FOCUS_LEAVES) {
+    var win = this.getEditableDomHelper().getWindow();
+    var dragging = false;
+    goog.events.unlistenByKey(this.listenForDragOverEventKey_);
+    this.listenForDragOverEventKey_ = goog.events.listenOnce(
+        win.document.body, 'dragover',
+        function() {
+          dragging = true;
+        });
+    goog.global.setTimeout(goog.bind(function() {
+      // Do not clear the selection if we're only dragging text.
+      // This addresses a bug on FF1.5/linux where dragging fires a blur,
+      // but clearing the selection confuses Firefox's drag-and-drop
+      // implementation. For more info, see http://b/1061064
+      if (!dragging) {
+        if (this.editableDomHelper) {
+          var rng = this.getRange();
+
+          // If there are multiple fields on a page, we need to make sure that
+          // the selection isn't retained when we switch between fields. We
+          // could have collapsed the range but there is a bug in GECKO where
+          // the selection stays highlighted even though its backing range is
+          // collapsed (http://b/1390115). To get around this, we clear the
+          // selection and restore the collapsed range back in. Restoring the
+          // range is important so that the cursor stays intact when we tab out
+          // and into a field (See http://b/1790301 for additional details on
+          // this).
+          var iframeWindow = this.editableDomHelper.getWindow();
+          goog.dom.Range.clearSelection(iframeWindow);
+
+          if (rng) {
+            rng.collapse(true);
+            rng.select();
+          }
+        }
+      }
+    }, this), 0);
+  }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.turnOnDesignModeGecko = function() {
+  goog.editor.SeamlessField.superClass_.turnOnDesignModeGecko.call(this);
+  var doc = this.getEditableDomHelper().getDocument();
+
+  doc.execCommand('enableInlineTableEditing', false, 'false');
+  doc.execCommand('enableObjectResizing', false, 'false');
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.installStyles = function() {
+  if (!this.usesIframe()) {
+    if (!goog.editor.SeamlessField.haveInstalledCss_) {
+      if (this.cssStyles) {
+        goog.style.installStyles(this.cssStyles, this.getElement());
+      }
+
+      // TODO(user): this should be reset to false when the editor is quit.
+      // In non-iframe mode, CSS styles should only be instaled once.
+      goog.editor.SeamlessField.haveInstalledCss_ = true;
+    }
+  }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.makeEditableInternal = function(
+    opt_iframeSrc) {
+  if (this.usesIframe()) {
+    goog.editor.SeamlessField.superClass_.makeEditableInternal.call(this,
+        opt_iframeSrc);
+  } else {
+    var field = this.getOriginalElement();
+    if (field) {
+      this.setupFieldObject(field);
+      field.contentEditable = true;
+
+      this.injectContents(field.innerHTML, field);
+
+      this.handleFieldLoad();
+    }
+  }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.handleFieldLoad = function() {
+  if (this.usesIframe()) {
+    // If the CSS inheriting code screws up (e.g. makes fonts too large) and
+    // the field is sized off in goog.editor.Field.makeIframeField, then we need
+    // to size it correctly, but it needs to be visible for the browser
+    // to have fully rendered it. We need to put this on a timeout to give
+    // the browser time to render.
+    var self = this;
+    goog.global.setTimeout(function() {
+      self.doFieldSizingGecko();
+    }, 0);
+  }
+  goog.editor.SeamlessField.superClass_.handleFieldLoad.call(this);
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.getIframeAttributes = function() {
+  return { 'frameBorder': 0, 'style': 'padding:0;' };
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.attachIframe = function(iframe) {
+  this.autoDetectFixedHeight_();
+  var field = this.getOriginalElement();
+  var dh = goog.dom.getDomHelper(field);
+
+  // Grab the width/height values of the field before modifying any CSS
+  // as some of the modifications affect its size (e.g. innerHTML='')
+  // Here, we set the size of the field to fixed so there's not too much
+  // jiggling when we set the innerHTML of the field.
+  var oldWidth = field.style.width;
+  var oldHeight = field.style.height;
+  goog.style.setStyle(field, 'visibility', 'hidden');
+
+  // If there is a floated element at the bottom of the field,
+  // then it needs a clearing div at the end to cause the clientHeight
+  // to contain the entire field.
+  // Also, with css re-writing, the margins of the first/last
+  // paragraph don't seem to get included in the clientHeight. Specifically,
+  // the extra divs below force the field's clientHeight to include the
+  // margins on the first and last elements contained within it.
+  var startDiv = dh.createDom(goog.dom.TagName.DIV,
+      {'style': 'height:0;clear:both', 'innerHTML': '&nbsp;'});
+  var endDiv = startDiv.cloneNode(true);
+  field.insertBefore(startDiv, field.firstChild);
+  goog.dom.appendChild(field, endDiv);
+
+  var contentBox = goog.style.getContentBoxSize(field);
+  var width = contentBox.width;
+  var height = contentBox.height;
+
+  var html = '';
+  if (this.isFixedHeight()) {
+    html = '&nbsp;';
+
+    goog.style.setStyle(field, 'position', 'relative');
+    goog.style.setStyle(field, 'overflow', 'visible');
+
+    goog.style.setStyle(iframe, 'position', 'absolute');
+    goog.style.setStyle(iframe, 'top', '0');
+    goog.style.setStyle(iframe, 'left', '0');
+  }
+  goog.style.setSize(field, width, height);
+
+  // In strict mode, browsers put blank space at the bottom and right
+  // if a field when it has an iframe child, to fill up the remaining line
+  // height. So make the line height = 0.
+  if (goog.editor.node.isStandardsMode(field)) {
+    this.originalFieldLineHeight_ = field.style.lineHeight;
+    goog.style.setStyle(field, 'lineHeight', '0');
+  }
+
+  goog.editor.node.replaceInnerHtml(field, html);
+  // Set the initial size
+  goog.style.setSize(iframe, width, height);
+  goog.style.setSize(field, oldWidth, oldHeight);
+  goog.style.setStyle(field, 'visibility', '');
+  goog.dom.appendChild(field, iframe);
+
+  // Only write if its not IE HTTPS in which case we're waiting for load.
+  if (!this.shouldLoadAsynchronously()) {
+    var doc = iframe.contentWindow.document;
+    if (goog.editor.node.isStandardsMode(iframe.ownerDocument)) {
+      doc.open();
+      var emptyHtml = goog.html.uncheckedconversions
+          .safeHtmlFromStringKnownToSatisfyTypeContract(
+              goog.string.Const.from('HTML from constant string'),
+              '<!DOCTYPE HTML><html></html>');
+      goog.dom.safe.documentWrite(doc, emptyHtml);
+      doc.close();
+    }
+  }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.getFieldFormatInfo = function(
+    extraStyles) {
+  var originalElement = this.getOriginalElement();
+  if (originalElement) {
+    return new goog.editor.icontent.FieldFormatInfo(
+        this.id,
+        goog.editor.node.isStandardsMode(originalElement),
+        true,
+        this.isFixedHeight(),
+        extraStyles);
+  }
+  throw Error('no field');
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.writeIframeContent = function(
+    iframe, innerHtml, extraStyles) {
+  // For seamless iframes, hide the iframe while we're laying it out to
+  // prevent the flicker.
+  goog.style.setStyle(iframe, 'visibility', 'hidden');
+  var formatInfo = this.getFieldFormatInfo(extraStyles);
+  var styleInfo = new goog.editor.icontent.FieldStyleInfo(
+      this.getOriginalElement(),
+      this.cssStyles + this.getIframeableCss());
+  goog.editor.icontent.writeNormalInitialBlendedIframe(
+      formatInfo, innerHtml, styleInfo, iframe);
+  this.doFieldSizingGecko();
+  goog.style.setStyle(iframe, 'visibility', 'visible');
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.restoreDom = function() {
+  // TODO(user): Consider only removing the iframe if we are
+  // restoring the original node.
+  if (this.usesIframe()) {
+    goog.dom.removeNode(this.getEditableIframe());
+  }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.clearListeners = function() {
+  goog.events.unlistenByKey(this.listenForDragOverEventKey_);
+  goog.events.unlistenByKey(this.listenForIframeLoadEventKey_);
+
+  goog.editor.SeamlessField.base(this, 'clearListeners');
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/style.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/style.js b/externs/GCL/externs/goog/editor/style.js
new file mode 100644
index 0000000..55b703b
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/style.js
@@ -0,0 +1,225 @@
+// Copyright 2009 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilties for working with the styles of DOM nodes, and
+ * related to rich text editing.
+ *
+ * Many of these are not general enough to go into goog.style, and use
+ * constructs (like "isContainer") that only really make sense inside
+ * of an HTML editor.
+ *
+ * The API has been optimized for iterating over large, irregular DOM
+ * structures (with lots of text nodes), and so the API tends to be a bit
+ * more permissive than the goog.style API should be. For example,
+ * goog.style.getComputedStyle will throw an exception if you give it a
+ * text node.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+goog.provide('goog.editor.style');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.TagName');
+goog.require('goog.editor.BrowserFeature');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+
+
+/**
+ * Gets the computed or cascaded style.
+ *
+ * This is different than goog.style.getStyle_ because it returns null
+ * for text nodes (instead of throwing an exception), and never reads
+ * inline style. These two functions may need to be reconciled.
+ *
+ * @param {!Node} node Node to get style of.
+ * @param {string} stylePropertyName Property to get (must be camelCase,
+ *     not css-style).
+ * @return {?string} Style value, or null if this is not an element node.
+ * @private
+ */
+goog.editor.style.getComputedOrCascadedStyle_ = function(
+    node, stylePropertyName) {
+  if (node.nodeType != goog.dom.NodeType.ELEMENT) {
+    // Only element nodes have style.
+    return null;
+  }
+  return goog.userAgent.IE ?
+      goog.style.getCascadedStyle(/** @type {!Element} */ (node),
+          stylePropertyName) :
+      goog.style.getComputedStyle(/** @type {!Element} */ (node),
+          stylePropertyName);
+};
+
+
+/**
+ * Checks whether the given element inherits display: block.
+ * @param {!Node} node The Node to check.
+ * @return {boolean} Whether the element inherits CSS display: block.
+ */
+goog.editor.style.isDisplayBlock = function(node) {
+  return goog.editor.style.getComputedOrCascadedStyle_(
+      node, 'display') == 'block';
+};
+
+
+/**
+ * Returns true if the element is a container of other non-inline HTML
+ * Note that span, strong and em tags, being inline can only contain
+ * other inline elements and are thus, not containers. Containers are elements
+ * that should not be broken up when wrapping selections with a node of an
+ * inline block styling.
+ * @param {Node} element The element to check.
+ * @return {boolean} Whether the element is a container.
+ */
+goog.editor.style.isContainer = function(element) {
+  var nodeName = element && element.nodeName;
+  return !!(element &&
+            (goog.editor.style.isDisplayBlock(element) ||
+             nodeName == goog.dom.TagName.TD ||
+             nodeName == goog.dom.TagName.TABLE ||
+             nodeName == goog.dom.TagName.LI));
+};
+
+
+/**
+ * Return the first ancestor of this node that is a container, inclusive.
+ * @see isContainer
+ * @param {Node} node Node to find the container of.
+ * @return {Element} The element which contains node.
+ */
+goog.editor.style.getContainer = function(node) {
+  // We assume that every node must have a container.
+  return /** @type {Element} */ (
+      goog.dom.getAncestor(node, goog.editor.style.isContainer, true));
+};
+
+
+/**
+ * Set of input types that should be kept selectable even when their ancestors
+ * are made unselectable.
+ * @type {Object}
+ * @private
+ */
+goog.editor.style.SELECTABLE_INPUT_TYPES_ = goog.object.createSet(
+    'text', 'file', 'url');
+
+
+/**
+ * Prevent the default action on mousedown events.
+ * @param {goog.events.Event} e The mouse down event.
+ * @private
+ */
+goog.editor.style.cancelMouseDownHelper_ = function(e) {
+  var targetTagName = e.target.tagName;
+  if (targetTagName != goog.dom.TagName.TEXTAREA &&
+      targetTagName != goog.dom.TagName.INPUT) {
+    e.preventDefault();
+  }
+};
+
+
+/**
+ * Makes the given element unselectable, as well as all of its children, except
+ * for text areas, text, file and url inputs.
+ * @param {Element} element The element to make unselectable.
+ * @param {goog.events.EventHandler} eventHandler An EventHandler to register
+ *     the event with. Assumes when the node is destroyed, the eventHandler's
+ *     listeners are destroyed as well.
+ */
+goog.editor.style.makeUnselectable = function(element, eventHandler) {
+  if (goog.editor.BrowserFeature.HAS_UNSELECTABLE_STYLE) {
+    // The mousing down on a node should not blur the focused node.
+    // This is consistent with how IE works.
+    // TODO: Consider using just the mousedown handler and not the css property.
+    eventHandler.listen(element, goog.events.EventType.MOUSEDOWN,
+        goog.editor.style.cancelMouseDownHelper_, true);
+  }
+
+  goog.style.setUnselectable(element, true);
+
+  // Make inputs and text areas selectable.
+  var inputs = element.getElementsByTagName(goog.dom.TagName.INPUT);
+  for (var i = 0, len = inputs.length; i < len; i++) {
+    var input = inputs[i];
+    if (input.type in goog.editor.style.SELECTABLE_INPUT_TYPES_) {
+      goog.editor.style.makeSelectable(input);
+    }
+  }
+  goog.array.forEach(element.getElementsByTagName(goog.dom.TagName.TEXTAREA),
+      goog.editor.style.makeSelectable);
+};
+
+
+/**
+ * Make the given element selectable.
+ *
+ * For IE this simply turns off the "unselectable" property.
+ *
+ * Under FF no descendent of an unselectable node can be selectable:
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=203291
+ *
+ * So we make each ancestor of node selectable, while trying to preserve the
+ * unselectability of other nodes along that path
+ *
+ * This may cause certain text nodes which should be unselectable, to become
+ * selectable. For example:
+ *
+ * <div id=div1 style="-moz-user-select: none">
+ *   Text1
+ *   <span id=span1>Text2</span>
+ * </div>
+ *
+ * If we call makeSelectable on span1, then it will cause "Text1" to become
+ * selectable, since it had to make div1 selectable in order for span1 to be
+ * selectable.
+ *
+ * If "Text1" were enclosed within a <p> or <span>, then this problem would
+ * not arise.  Text nodes do not have styles, so its style can't be set to
+ * unselectable.
+ *
+ * @param {Element} element The element to make selectable.
+ */
+goog.editor.style.makeSelectable = function(element) {
+  goog.style.setUnselectable(element, false);
+  if (goog.editor.BrowserFeature.HAS_UNSELECTABLE_STYLE) {
+    // Go up ancestor chain, searching for nodes that are unselectable.
+    // If such a node exists, mark it as selectable but mark its other children
+    // as unselectable so the minimum set of nodes is changed.
+    var child = element;
+    var current = /** @type {Element} */ (element.parentNode);
+    while (current && current.tagName != goog.dom.TagName.HTML) {
+      if (goog.style.isUnselectable(current)) {
+        goog.style.setUnselectable(current, false, true);
+
+        for (var i = 0, len = current.childNodes.length; i < len; i++) {
+          var node = current.childNodes[i];
+          if (node != child && node.nodeType == goog.dom.NodeType.ELEMENT) {
+            goog.style.setUnselectable(current.childNodes[i], true);
+          }
+        }
+      }
+
+      child = current;
+      current = /** @type {Element} */ (current.parentNode);
+    }
+  }
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/table.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/table.js b/externs/GCL/externs/goog/editor/table.js
new file mode 100644
index 0000000..1bddc6d
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/table.js
@@ -0,0 +1,570 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Table editing support.
+ * This file provides the class goog.editor.Table and two
+ * supporting classes, goog.editor.TableRow and
+ * goog.editor.TableCell. Together these provide support for
+ * high level table modifications: Adding and deleting rows and columns,
+ * and merging and splitting cells.
+ *
+ * @supported IE6+, WebKit 525+, Firefox 2+.
+ */
+
+goog.provide('goog.editor.Table');
+goog.provide('goog.editor.TableCell');
+goog.provide('goog.editor.TableRow');
+
+goog.require('goog.dom');
+goog.require('goog.dom.DomHelper');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.TagName');
+goog.require('goog.log');
+goog.require('goog.string.Unicode');
+goog.require('goog.style');
+
+
+
+/**
+ * Class providing high level table editing functions.
+ * @param {Element} node Element that is a table or descendant of a table.
+ * @constructor
+ * @final
+ */
+goog.editor.Table = function(node) {
+  this.element = goog.dom.getAncestorByTagNameAndClass(node,
+      goog.dom.TagName.TABLE);
+  if (!this.element) {
+    goog.log.error(this.logger_,
+        "Can't create Table based on a node " +
+        "that isn't a table, or descended from a table.");
+  }
+  this.dom_ = goog.dom.getDomHelper(this.element);
+  this.refresh();
+};
+
+
+/**
+ * Logger object for debugging and error messages.
+ * @type {goog.log.Logger}
+ * @private
+ */
+goog.editor.Table.prototype.logger_ =
+    goog.log.getLogger('goog.editor.Table');
+
+
+/**
+ * Walks the dom structure of this object's table element and populates
+ * this.rows with goog.editor.TableRow objects. This is done initially
+ * to populate the internal data structures, and also after each time the
+ * DOM structure is modified. Currently this means that the all existing
+ * information is discarded and re-read from the DOM.
+ */
+// TODO(user): support partial refresh to save cost of full update
+// every time there is a change to the DOM.
+goog.editor.Table.prototype.refresh = function() {
+  var rows = this.rows = [];
+  var tbody = this.element.getElementsByTagName(goog.dom.TagName.TBODY)[0];
+  if (!tbody) {
+    return;
+  }
+  var trs = [];
+  for (var child = tbody.firstChild; child; child = child.nextSibling) {
+    if (child.nodeName == goog.dom.TagName.TR) {
+      trs.push(child);
+    }
+  }
+
+  for (var rowNum = 0, tr; tr = trs[rowNum]; rowNum++) {
+    var existingRow = rows[rowNum];
+    var tds = goog.editor.Table.getChildCellElements(tr);
+    var columnNum = 0;
+    // A note on cellNum vs. columnNum: A cell is a td/th element. Cells may
+    // use colspan/rowspan to extend over multiple rows/columns. cellNum
+    // is the dom element number, columnNum is the logical column number.
+    for (var cellNum = 0, td; td = tds[cellNum]; cellNum++) {
+      // If there's already a cell extending into this column
+      // (due to that cell's colspan/rowspan), increment the column counter.
+      while (existingRow && existingRow.columns[columnNum]) {
+        columnNum++;
+      }
+      var cell = new goog.editor.TableCell(td, rowNum, columnNum);
+      // Place this cell in every row and column into which it extends.
+      for (var i = 0; i < cell.rowSpan; i++) {
+        var cellRowNum = rowNum + i;
+        // Create TableRow objects in this.rows as needed.
+        var cellRow = rows[cellRowNum];
+        if (!cellRow) {
+          // TODO(user): try to avoid second trs[] lookup.
+          rows.push(
+              cellRow = new goog.editor.TableRow(trs[cellRowNum], cellRowNum));
+        }
+        // Extend length of column array to make room for this cell.
+        var minimumColumnLength = columnNum + cell.colSpan;
+        if (cellRow.columns.length < minimumColumnLength) {
+          cellRow.columns.length = minimumColumnLength;
+        }
+        for (var j = 0; j < cell.colSpan; j++) {
+          var cellColumnNum = columnNum + j;
+          cellRow.columns[cellColumnNum] = cell;
+        }
+      }
+      columnNum += cell.colSpan;
+    }
+  }
+};
+
+
+/**
+ * Returns all child elements of a TR element that are of type TD or TH.
+ * @param {Element} tr TR element in which to find children.
+ * @return {!Array<Element>} array of child cell elements.
+ */
+goog.editor.Table.getChildCellElements = function(tr) {
+  var cells = [];
+  for (var i = 0, cell; cell = tr.childNodes[i]; i++) {
+    if (cell.nodeName == goog.dom.TagName.TD ||
+        cell.nodeName == goog.dom.TagName.TH) {
+      cells.push(cell);
+    }
+  }
+  return cells;
+};
+
+
+/**
+ * Inserts a new row in the table. The row will be populated with new
+ * cells, and existing rowspanned cells that overlap the new row will
+ * be extended.
+ * @param {number=} opt_rowIndex Index at which to insert the row. If
+ *     this is omitted the row will be appended to the end of the table.
+ * @return {!Element} The new row.
+ */
+goog.editor.Table.prototype.insertRow = function(opt_rowIndex) {
+  var rowIndex = goog.isDefAndNotNull(opt_rowIndex) ?
+      opt_rowIndex : this.rows.length;
+  var refRow;
+  var insertAfter;
+  if (rowIndex == 0) {
+    refRow = this.rows[0];
+    insertAfter = false;
+  } else {
+    refRow = this.rows[rowIndex - 1];
+    insertAfter = true;
+  }
+  var newTr = this.dom_.createElement(goog.dom.TagName.TR);
+  for (var i = 0, cell; cell = refRow.columns[i]; i += 1) {
+    // Check whether the existing cell will span this new row.
+    // If so, instead of creating a new cell, extend
+    // the rowspan of the existing cell.
+    if ((insertAfter && cell.endRow > rowIndex) ||
+        (!insertAfter && cell.startRow < rowIndex)) {
+      cell.setRowSpan(cell.rowSpan + 1);
+      if (cell.colSpan > 1) {
+        i += cell.colSpan - 1;
+      }
+    } else {
+      newTr.appendChild(this.createEmptyTd());
+    }
+    if (insertAfter) {
+      goog.dom.insertSiblingAfter(newTr, refRow.element);
+    } else {
+      goog.dom.insertSiblingBefore(newTr, refRow.element);
+    }
+  }
+  this.refresh();
+  return newTr;
+};
+
+
+/**
+ * Inserts a new column in the table. The column will be created by
+ * inserting new TD elements in each row, or extending the colspan
+ * of existing TD elements.
+ * @param {number=} opt_colIndex Index at which to insert the column. If
+ *     this is omitted the column will be appended to the right side of
+ *     the table.
+ * @return {!Array<Element>} Array of new cell elements that were created
+ *     to populate the new column.
+ */
+goog.editor.Table.prototype.insertColumn = function(opt_colIndex) {
+  // TODO(user): set column widths in a way that makes sense.
+  var colIndex = goog.isDefAndNotNull(opt_colIndex) ?
+      opt_colIndex :
+      (this.rows[0] && this.rows[0].columns.length) || 0;
+  var newTds = [];
+  for (var rowNum = 0, row; row = this.rows[rowNum]; rowNum++) {
+    var existingCell = row.columns[colIndex];
+    if (existingCell && existingCell.endCol >= colIndex &&
+        existingCell.startCol < colIndex) {
+      existingCell.setColSpan(existingCell.colSpan + 1);
+      rowNum += existingCell.rowSpan - 1;
+    } else {
+      var newTd = this.createEmptyTd();
+      // TODO(user): figure out a way to intelligently size new columns.
+      newTd.style.width = goog.editor.Table.OPTIMUM_EMPTY_CELL_WIDTH + 'px';
+      this.insertCellElement(newTd, rowNum, colIndex);
+      newTds.push(newTd);
+    }
+  }
+  this.refresh();
+  return newTds;
+};
+
+
+/**
+ * Removes a row from the table, removing the TR element and
+ * decrementing the rowspan of any cells in other rows that overlap the row.
+ * @param {number} rowIndex Index of the row to delete.
+ */
+goog.editor.Table.prototype.removeRow = function(rowIndex) {
+  var row = this.rows[rowIndex];
+  if (!row) {
+    goog.log.warning(this.logger_,
+        "Can't remove row at position " + rowIndex + ': no such row.');
+  }
+  for (var i = 0, cell; cell = row.columns[i]; i += cell.colSpan) {
+    if (cell.rowSpan > 1) {
+      cell.setRowSpan(cell.rowSpan - 1);
+      if (cell.startRow == rowIndex) {
+        // Rowspanned cell started in this row - move it down to the next row.
+        this.insertCellElement(cell.element, rowIndex + 1, cell.startCol);
+      }
+    }
+  }
+  row.element.parentNode.removeChild(row.element);
+  this.refresh();
+};
+
+
+/**
+ * Removes a column from the table. This is done by removing cell elements,
+ * or shrinking the colspan of elements that span multiple columns.
+ * @param {number} colIndex Index of the column to delete.
+ */
+goog.editor.Table.prototype.removeColumn = function(colIndex) {
+  for (var i = 0, row; row = this.rows[i]; i++) {
+    var cell = row.columns[colIndex];
+    if (!cell) {
+      goog.log.error(this.logger_,
+          "Can't remove cell at position " + i + ', ' + colIndex +
+          ': no such cell.');
+    }
+    if (cell.colSpan > 1) {
+      cell.setColSpan(cell.colSpan - 1);
+    } else {
+      cell.element.parentNode.removeChild(cell.element);
+    }
+    // Skip over following rows that contain this same cell.
+    i += cell.rowSpan - 1;
+  }
+  this.refresh();
+};
+
+
+/**
+ * Merges multiple cells into a single cell, and sets the rowSpan and colSpan
+ * attributes of the cell to take up the same space as the original cells.
+ * @param {number} startRowIndex Top coordinate of the cells to merge.
+ * @param {number} startColIndex Left coordinate of the cells to merge.
+ * @param {number} endRowIndex Bottom coordinate of the cells to merge.
+ * @param {number} endColIndex Right coordinate of the cells to merge.
+ * @return {boolean} Whether or not the merge was possible. If the cells
+ *     in the supplied coordinates can't be merged this will return false.
+ */
+goog.editor.Table.prototype.mergeCells = function(
+    startRowIndex, startColIndex, endRowIndex, endColIndex) {
+  // TODO(user): take a single goog.math.Rect parameter instead?
+  var cells = [];
+  var cell;
+  if (startRowIndex == endRowIndex && startColIndex == endColIndex) {
+    goog.log.warning(this.logger_, "Can't merge single cell");
+    return false;
+  }
+  // Gather cells and do sanity check.
+  for (var i = startRowIndex; i <= endRowIndex; i++) {
+    for (var j = startColIndex; j <= endColIndex; j++) {
+      cell = this.rows[i].columns[j];
+      if (cell.startRow < startRowIndex ||
+          cell.endRow > endRowIndex ||
+          cell.startCol < startColIndex ||
+          cell.endCol > endColIndex) {
+        goog.log.warning(this.logger_,
+            "Can't merge cells: the cell in row " + i + ', column ' + j +
+            'extends outside the supplied rectangle.');
+        return false;
+      }
+      // TODO(user): this is somewhat inefficient, as we will add
+      // a reference for a cell for each position, even if it's a single
+      // cell with row/colspan.
+      cells.push(cell);
+    }
+  }
+  var targetCell = cells[0];
+  var targetTd = targetCell.element;
+  var doc = this.dom_.getDocument();
+
+  // Merge cell contents and discard other cells.
+  for (var i = 1; cell = cells[i]; i++) {
+    var td = cell.element;
+    if (!td.parentNode || td == targetTd) {
+      // We've already handled this cell at one of its previous positions.
+      continue;
+    }
+    // Add a space if needed, to keep merged content from getting squished
+    // together.
+    if (targetTd.lastChild &&
+        targetTd.lastChild.nodeType == goog.dom.NodeType.TEXT) {
+      targetTd.appendChild(doc.createTextNode(' '));
+    }
+    var childNode;
+    while ((childNode = td.firstChild)) {
+      targetTd.appendChild(childNode);
+    }
+    td.parentNode.removeChild(td);
+  }
+  targetCell.setColSpan((endColIndex - startColIndex) + 1);
+  targetCell.setRowSpan((endRowIndex - startRowIndex) + 1);
+  if (endColIndex > startColIndex) {
+    // Clear width on target cell.
+    // TODO(user): instead of clearing width, calculate width
+    // based on width of input cells
+    targetTd.removeAttribute('width');
+    targetTd.style.width = null;
+  }
+  this.refresh();
+
+  return true;
+};
+
+
+/**
+ * Splits a cell with colspans or rowspans into multiple descrete cells.
+ * @param {number} rowIndex y coordinate of the cell to split.
+ * @param {number} colIndex x coordinate of the cell to split.
+ * @return {!Array<Element>} Array of new cell elements created by splitting
+ *     the cell.
+ */
+// TODO(user): support splitting only horizontally or vertically,
+// support splitting cells that aren't already row/colspanned.
+goog.editor.Table.prototype.splitCell = function(rowIndex, colIndex) {
+  var row = this.rows[rowIndex];
+  var cell = row.columns[colIndex];
+  var newTds = [];
+  for (var i = 0; i < cell.rowSpan; i++) {
+    for (var j = 0; j < cell.colSpan; j++) {
+      if (i > 0 || j > 0) {
+        var newTd = this.createEmptyTd();
+        this.insertCellElement(newTd, rowIndex + i, colIndex + j);
+        newTds.push(newTd);
+      }
+    }
+  }
+  cell.setColSpan(1);
+  cell.setRowSpan(1);
+  this.refresh();
+  return newTds;
+};
+
+
+/**
+ * Inserts a cell element at the given position. The colIndex is the logical
+ * column index, not the position in the dom. This takes into consideration
+ * that cells in a given logical  row may actually be children of a previous
+ * DOM row that have used rowSpan to extend into the row.
+ * @param {Element} td The new cell element to insert.
+ * @param {number} rowIndex Row in which to insert the element.
+ * @param {number} colIndex Column in which to insert the element.
+ */
+goog.editor.Table.prototype.insertCellElement = function(
+    td, rowIndex, colIndex) {
+  var row = this.rows[rowIndex];
+  var nextSiblingElement = null;
+  for (var i = colIndex, cell; cell = row.columns[i]; i += cell.colSpan) {
+    if (cell.startRow == rowIndex) {
+      nextSiblingElement = cell.element;
+      break;
+    }
+  }
+  row.element.insertBefore(td, nextSiblingElement);
+};
+
+
+/**
+ * Creates an empty TD element and fill it with some empty content so it will
+ * show up with borders even in IE pre-7 or if empty-cells is set to 'hide'
+ * @return {!Element} a new TD element.
+ */
+goog.editor.Table.prototype.createEmptyTd = function() {
+  // TODO(user): more cross-browser testing to determine best
+  // and least annoying filler content.
+  return this.dom_.createDom(goog.dom.TagName.TD, {}, goog.string.Unicode.NBSP);
+};
+
+
+
+/**
+ * Class representing a logical table row: a tr element and any cells
+ * that appear in that row.
+ * @param {Element} trElement This rows's underlying TR element.
+ * @param {number} rowIndex This row's index in its parent table.
+ * @constructor
+ * @final
+ */
+goog.editor.TableRow = function(trElement, rowIndex) {
+  this.index = rowIndex;
+  this.element = trElement;
+  this.columns = [];
+};
+
+
+
+/**
+ * Class representing a table cell, which may span across multiple
+ * rows and columns
+ * @param {Element} td This cell's underlying TD or TH element.
+ * @param {number} startRow Index of the row where this cell begins.
+ * @param {number} startCol Index of the column where this cell begins.
+ * @constructor
+ * @final
+ */
+goog.editor.TableCell = function(td, startRow, startCol) {
+  this.element = td;
+  this.colSpan = parseInt(td.colSpan, 10) || 1;
+  this.rowSpan = parseInt(td.rowSpan, 10) || 1;
+  this.startRow = startRow;
+  this.startCol = startCol;
+  this.updateCoordinates_();
+};
+
+
+/**
+ * Calculates this cell's endRow/endCol coordinates based on rowSpan/colSpan
+ * @private
+ */
+goog.editor.TableCell.prototype.updateCoordinates_ = function() {
+  this.endCol = this.startCol + this.colSpan - 1;
+  this.endRow = this.startRow + this.rowSpan - 1;
+};
+
+
+/**
+ * Set this cell's colSpan, updating both its colSpan property and the
+ * underlying element's colSpan attribute.
+ * @param {number} colSpan The new colSpan.
+ */
+goog.editor.TableCell.prototype.setColSpan = function(colSpan) {
+  if (colSpan != this.colSpan) {
+    if (colSpan > 1) {
+      this.element.colSpan = colSpan;
+    } else {
+      this.element.colSpan = 1,
+      this.element.removeAttribute('colSpan');
+    }
+    this.colSpan = colSpan;
+    this.updateCoordinates_();
+  }
+};
+
+
+/**
+ * Set this cell's rowSpan, updating both its rowSpan property and the
+ * underlying element's rowSpan attribute.
+ * @param {number} rowSpan The new rowSpan.
+ */
+goog.editor.TableCell.prototype.setRowSpan = function(rowSpan) {
+  if (rowSpan != this.rowSpan) {
+    if (rowSpan > 1) {
+      this.element.rowSpan = rowSpan.toString();
+    } else {
+      this.element.rowSpan = '1';
+      this.element.removeAttribute('rowSpan');
+    }
+    this.rowSpan = rowSpan;
+    this.updateCoordinates_();
+  }
+};
+
+
+/**
+ * Optimum size of empty cells (in pixels), if possible.
+ * @type {number}
+ */
+goog.editor.Table.OPTIMUM_EMPTY_CELL_WIDTH = 60;
+
+
+/**
+ * Maximum width for new tables.
+ * @type {number}
+ */
+goog.editor.Table.OPTIMUM_MAX_NEW_TABLE_WIDTH = 600;
+
+
+/**
+ * Default color for table borders.
+ * @type {string}
+ */
+goog.editor.Table.DEFAULT_BORDER_COLOR = '#888';
+
+
+/**
+ * Creates a new table element, populated with cells and formatted.
+ * @param {Document} doc Document in which to create the table element.
+ * @param {number} columns Number of columns in the table.
+ * @param {number} rows Number of rows in the table.
+ * @param {Object=} opt_tableStyle Object containing borderWidth and borderColor
+ *    properties, used to set the inital style of the table.
+ * @return {!Element} a table element.
+ */
+goog.editor.Table.createDomTable = function(
+    doc, columns, rows, opt_tableStyle) {
+  // TODO(user): define formatting properties as constants,
+  // make separate formatTable() function
+  var style = {
+    borderWidth: '1',
+    borderColor: goog.editor.Table.DEFAULT_BORDER_COLOR
+  };
+  for (var prop in opt_tableStyle) {
+    style[prop] = opt_tableStyle[prop];
+  }
+  var dom = new goog.dom.DomHelper(doc);
+  var tableElement = dom.createTable(rows, columns, true);
+
+  var minimumCellWidth = 10;
+  // Calculate a good cell width.
+  var cellWidth = Math.max(
+      minimumCellWidth,
+      Math.min(goog.editor.Table.OPTIMUM_EMPTY_CELL_WIDTH,
+               goog.editor.Table.OPTIMUM_MAX_NEW_TABLE_WIDTH / columns));
+
+  var tds = tableElement.getElementsByTagName(goog.dom.TagName.TD);
+  for (var i = 0, td; td = tds[i]; i++) {
+    td.style.width = cellWidth + 'px';
+  }
+
+  // Set border somewhat redundantly to make sure they show
+  // up correctly in all browsers.
+  goog.style.setStyle(
+      tableElement, {
+        'borderCollapse': 'collapse',
+        'borderColor': style.borderColor,
+        'borderWidth': style.borderWidth + 'px'
+      });
+  tableElement.border = style.borderWidth;
+  tableElement.setAttribute('bordercolor', style.borderColor);
+  tableElement.setAttribute('cellspacing', '0');
+
+  return tableElement;
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/actioneventwrapper.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/events/actioneventwrapper.js b/externs/GCL/externs/goog/events/actioneventwrapper.js
new file mode 100644
index 0000000..779150f
--- /dev/null
+++ b/externs/GCL/externs/goog/events/actioneventwrapper.js
@@ -0,0 +1,151 @@
+// Copyright 2009 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Action event wrapper implementation.
+ * @author eae@google.com (Emil A Eklund)
+ */
+
+goog.provide('goog.events.actionEventWrapper');
+
+goog.require('goog.a11y.aria');
+goog.require('goog.a11y.aria.Role');
+goog.require('goog.dom');
+goog.require('goog.events');
+/** @suppress {extraRequire} */
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventType');
+goog.require('goog.events.EventWrapper');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Event wrapper for action handling. Fires when an element is activated either
+ * by clicking it or by focusing it and pressing Enter.
+ *
+ * @constructor
+ * @implements {goog.events.EventWrapper}
+ * @private
+ */
+goog.events.ActionEventWrapper_ = function() {
+};
+
+
+/**
+ * Singleton instance of ActionEventWrapper_.
+ * @type {goog.events.ActionEventWrapper_}
+ */
+goog.events.actionEventWrapper = new goog.events.ActionEventWrapper_();
+
+
+/**
+ * Event types used by the wrapper.
+ *
+ * @type {Array<goog.events.EventType>}
+ * @private
+ */
+goog.events.ActionEventWrapper_.EVENT_TYPES_ = [
+  goog.events.EventType.CLICK,
+  goog.userAgent.GECKO ?
+      goog.events.EventType.KEYPRESS : goog.events.EventType.KEYDOWN,
+  goog.events.EventType.KEYUP
+];
+
+
+/**
+ * Adds an event listener using the wrapper on a DOM Node or an object that has
+ * implemented {@link goog.events.EventTarget}. A listener can only be added
+ * once to an object.
+ *
+ * @param {goog.events.ListenableType} target The target to listen to events on.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
+ *     method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ *     false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @param {goog.events.EventHandler=} opt_eventHandler Event handler to add
+ *     listener to.
+ * @override
+ */
+goog.events.ActionEventWrapper_.prototype.listen = function(target, listener,
+    opt_capt, opt_scope, opt_eventHandler) {
+  var callback = function(e) {
+    var listenerFn = goog.events.wrapListener(listener);
+    var role = goog.dom.isElement(e.target) ?
+        goog.a11y.aria.getRole(/** @type {!Element} */ (e.target)) : null;
+    if (e.type == goog.events.EventType.CLICK && e.isMouseActionButton()) {
+      listenerFn.call(opt_scope, e);
+    } else if ((e.keyCode == goog.events.KeyCodes.ENTER ||
+        e.keyCode == goog.events.KeyCodes.MAC_ENTER) &&
+        e.type != goog.events.EventType.KEYUP) {
+      // convert keydown to keypress for backward compatibility.
+      e.type = goog.events.EventType.KEYPRESS;
+      listenerFn.call(opt_scope, e);
+    } else if (e.keyCode == goog.events.KeyCodes.SPACE &&
+        e.type == goog.events.EventType.KEYUP &&
+        (role == goog.a11y.aria.Role.BUTTON ||
+            role == goog.a11y.aria.Role.TAB)) {
+      listenerFn.call(opt_scope, e);
+      e.preventDefault();
+    }
+  };
+  callback.listener_ = listener;
+  callback.scope_ = opt_scope;
+
+  if (opt_eventHandler) {
+    opt_eventHandler.listen(target,
+        goog.events.ActionEventWrapper_.EVENT_TYPES_,
+        callback, opt_capt);
+  } else {
+    goog.events.listen(target,
+        goog.events.ActionEventWrapper_.EVENT_TYPES_,
+        callback, opt_capt);
+  }
+};
+
+
+/**
+ * Removes an event listener added using goog.events.EventWrapper.listen.
+ *
+ * @param {goog.events.ListenableType} target The node to remove listener from.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
+ *     method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ *     false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove
+ *     listener from.
+ * @override
+ */
+goog.events.ActionEventWrapper_.prototype.unlisten = function(target, listener,
+    opt_capt, opt_scope, opt_eventHandler) {
+  for (var type, j = 0; type = goog.events.ActionEventWrapper_.EVENT_TYPES_[j];
+      j++) {
+    var listeners = goog.events.getListeners(target, type, !!opt_capt);
+    for (var obj, i = 0; obj = listeners[i]; i++) {
+      if (obj.listener.listener_ == listener &&
+          obj.listener.scope_ == opt_scope) {
+        if (opt_eventHandler) {
+          opt_eventHandler.unlisten(target, type, obj.listener, opt_capt,
+              opt_scope);
+        } else {
+          goog.events.unlisten(target, type, obj.listener, opt_capt, opt_scope);
+        }
+        break;
+      }
+    }
+  }
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/actionhandler.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/events/actionhandler.js b/externs/GCL/externs/goog/events/actionhandler.js
new file mode 100644
index 0000000..190cc26
--- /dev/null
+++ b/externs/GCL/externs/goog/events/actionhandler.js
@@ -0,0 +1,184 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview This file contains a class to provide a unified mechanism for
+ * CLICK and enter KEYDOWN events. This provides better accessibility by
+ * providing the given functionality to a keyboard user which is otherwise
+ * would be available only via a mouse click.
+ *
+ * If there is an existing CLICK listener or planning to be added as below -
+ *
+ * <code>this.eventHandler_.listen(el, CLICK, this.onClick_);<code>
+ *
+ * it can be replaced with an ACTION listener as follows:
+ *
+ * <code>this.eventHandler_.listen(
+ *    new goog.events.ActionHandler(el),
+ *    ACTION,
+ *    this.onAction_);<code>
+ *
+ */
+
+goog.provide('goog.events.ActionEvent');
+goog.provide('goog.events.ActionHandler');
+goog.provide('goog.events.ActionHandler.EventType');
+goog.provide('goog.events.BeforeActionEvent');
+
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * A wrapper around an element that you want to listen to ACTION events on.
+ * @param {Element|Document} element The element or document to listen on.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @final
+ */
+goog.events.ActionHandler = function(element) {
+  goog.events.EventTarget.call(this);
+
+  /**
+   * This is the element that we will listen to events on.
+   * @type {Element|Document}
+   * @private
+   */
+  this.element_ = element;
+
+  goog.events.listen(element, goog.events.ActionHandler.KEY_EVENT_TYPE_,
+      this.handleKeyDown_, false, this);
+  goog.events.listen(element, goog.events.EventType.CLICK,
+      this.handleClick_, false, this);
+};
+goog.inherits(goog.events.ActionHandler, goog.events.EventTarget);
+
+
+/**
+ * Enum type for the events fired by the action handler
+ * @enum {string}
+ */
+goog.events.ActionHandler.EventType = {
+  ACTION: 'action',
+  BEFOREACTION: 'beforeaction'
+};
+
+
+/**
+ * Key event type to listen for.
+ * @type {string}
+ * @private
+ */
+goog.events.ActionHandler.KEY_EVENT_TYPE_ = goog.userAgent.GECKO ?
+    goog.events.EventType.KEYPRESS :
+    goog.events.EventType.KEYDOWN;
+
+
+/**
+ * Handles key press events.
+ * @param {!goog.events.BrowserEvent} e The key press event.
+ * @private
+ */
+goog.events.ActionHandler.prototype.handleKeyDown_ = function(e) {
+  if (e.keyCode == goog.events.KeyCodes.ENTER ||
+      goog.userAgent.WEBKIT && e.keyCode == goog.events.KeyCodes.MAC_ENTER) {
+    this.dispatchEvents_(e);
+  }
+};
+
+
+/**
+ * Handles mouse events.
+ * @param {!goog.events.BrowserEvent} e The click event.
+ * @private
+ */
+goog.events.ActionHandler.prototype.handleClick_ = function(e) {
+  this.dispatchEvents_(e);
+};
+
+
+/**
+ * Dispatches BeforeAction and Action events to the element
+ * @param {!goog.events.BrowserEvent} e The event causing dispatches.
+ * @private
+ */
+goog.events.ActionHandler.prototype.dispatchEvents_ = function(e) {
+  var beforeActionEvent = new goog.events.BeforeActionEvent(e);
+
+  // Allow application specific logic here before the ACTION event.
+  // For example, Gmail uses this event to restore keyboard focus
+  if (!this.dispatchEvent(beforeActionEvent)) {
+    // If the listener swallowed the BEFOREACTION event, don't dispatch the
+    // ACTION event.
+    return;
+  }
+
+
+  // Wrap up original event and send it off
+  var actionEvent = new goog.events.ActionEvent(e);
+  try {
+    this.dispatchEvent(actionEvent);
+  } finally {
+    // Stop propagating the event
+    e.stopPropagation();
+  }
+};
+
+
+/** @override */
+goog.events.ActionHandler.prototype.disposeInternal = function() {
+  goog.events.ActionHandler.superClass_.disposeInternal.call(this);
+  goog.events.unlisten(this.element_, goog.events.ActionHandler.KEY_EVENT_TYPE_,
+      this.handleKeyDown_, false, this);
+  goog.events.unlisten(this.element_, goog.events.EventType.CLICK,
+      this.handleClick_, false, this);
+  delete this.element_;
+};
+
+
+
+/**
+ * This class is used for the goog.events.ActionHandler.EventType.ACTION event.
+ * @param {!goog.events.BrowserEvent} browserEvent Browser event object.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
+ */
+goog.events.ActionEvent = function(browserEvent) {
+  goog.events.BrowserEvent.call(this, browserEvent.getBrowserEvent());
+  this.type = goog.events.ActionHandler.EventType.ACTION;
+};
+goog.inherits(goog.events.ActionEvent, goog.events.BrowserEvent);
+
+
+
+/**
+ * This class is used for the goog.events.ActionHandler.EventType.BEFOREACTION
+ * event. BEFOREACTION gives a chance to the application so the keyboard focus
+ * can be restored back, if required.
+ * @param {!goog.events.BrowserEvent} browserEvent Browser event object.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
+ */
+goog.events.BeforeActionEvent = function(browserEvent) {
+  goog.events.BrowserEvent.call(this, browserEvent.getBrowserEvent());
+  this.type = goog.events.ActionHandler.EventType.BEFOREACTION;
+};
+goog.inherits(goog.events.BeforeActionEvent, goog.events.BrowserEvent);


Mime
View raw message