diff options
Diffstat (limited to 'contexts/data/lib/closure-library/closure/goog/dom/browserrange/ierange.js')
-rw-r--r-- | contexts/data/lib/closure-library/closure/goog/dom/browserrange/ierange.js | 955 |
1 files changed, 0 insertions, 955 deletions
diff --git a/contexts/data/lib/closure-library/closure/goog/dom/browserrange/ierange.js b/contexts/data/lib/closure-library/closure/goog/dom/browserrange/ierange.js deleted file mode 100644 index 1693c38..0000000 --- a/contexts/data/lib/closure-library/closure/goog/dom/browserrange/ierange.js +++ /dev/null @@ -1,955 +0,0 @@ -// 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 Definition of the IE browser specific range wrapper. - * - * DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead. - * - * @author robbyw@google.com (Robby Walker) - * @author ojan@google.com (Ojan Vafai) - * @author jparent@google.com (Julie Parent) - */ - - -goog.provide('goog.dom.browserrange.IeRange'); - -goog.require('goog.array'); -goog.require('goog.debug.Logger'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeIterator'); -goog.require('goog.dom.NodeType'); -goog.require('goog.dom.RangeEndpoint'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.browserrange.AbstractRange'); -goog.require('goog.iter'); -goog.require('goog.iter.StopIteration'); -goog.require('goog.string'); - - - -/** - * The constructor for IE specific browser ranges. - * @param {TextRange} range The range object. - * @param {Document} doc The document the range exists in. - * @constructor - * @extends {goog.dom.browserrange.AbstractRange} - */ -goog.dom.browserrange.IeRange = function(range, doc) { - /** - * The browser range object this class wraps. - * @type {TextRange} - * @private - */ - this.range_ = range; - - /** - * The document the range exists in. - * @type {Document} - * @private - */ - this.doc_ = doc; -}; -goog.inherits(goog.dom.browserrange.IeRange, - goog.dom.browserrange.AbstractRange); - - -/** - * Logging object. - * @type {goog.debug.Logger} - * @private - */ -goog.dom.browserrange.IeRange.logger_ = - goog.debug.Logger.getLogger('goog.dom.browserrange.IeRange'); - - -/** - * Returns a browser range spanning the given node's contents. - * @param {Node} node The node to select. - * @return {TextRange} A browser range spanning the node's contents. - * @private - */ -goog.dom.browserrange.IeRange.getBrowserRangeForNode_ = function(node) { - var nodeRange = goog.dom.getOwnerDocument(node).body.createTextRange(); - if (node.nodeType == goog.dom.NodeType.ELEMENT) { - // Elements are easy. - nodeRange.moveToElementText(node); - // Note(user) : If there are no child nodes of the element, the - // range.htmlText includes the element's outerHTML. The range created above - // is not collapsed, and should be collapsed explicitly. - // Example : node = <div></div> - // But if the node is sth like <br>, it shouldnt be collapsed. - if (goog.dom.browserrange.canContainRangeEndpoint(node) && - !node.childNodes.length) { - nodeRange.collapse(false); - } - } else { - // Text nodes are hard. - // Compute the offset from the nearest element related position. - var offset = 0; - var sibling = node; - while (sibling = sibling.previousSibling) { - var nodeType = sibling.nodeType; - if (nodeType == goog.dom.NodeType.TEXT) { - offset += sibling.length; - } else if (nodeType == goog.dom.NodeType.ELEMENT) { - // Move to the space after this element. - nodeRange.moveToElementText(sibling); - break; - } - } - - if (!sibling) { - nodeRange.moveToElementText(node.parentNode); - } - - nodeRange.collapse(!sibling); - - if (offset) { - nodeRange.move('character', offset); - } - - nodeRange.moveEnd('character', node.length); - } - - return nodeRange; -}; - - -/** - * Returns a browser range spanning the given nodes. - * @param {Node} startNode The node to start with. - * @param {number} startOffset The offset within the start node. - * @param {Node} endNode The node to end with. - * @param {number} endOffset The offset within the end node. - * @return {TextRange} A browser range spanning the node's contents. - * @private - */ -goog.dom.browserrange.IeRange.getBrowserRangeForNodes_ = function(startNode, - startOffset, endNode, endOffset) { - // Create a range starting at the correct start position. - var child, collapse = false; - if (startNode.nodeType == goog.dom.NodeType.ELEMENT) { - if (startOffset > startNode.childNodes.length) { - goog.dom.browserrange.IeRange.logger_.severe( - 'Cannot have startOffset > startNode child count'); - } - child = startNode.childNodes[startOffset]; - collapse = !child; - startNode = child || startNode.lastChild || startNode; - startOffset = 0; - } - var leftRange = goog.dom.browserrange.IeRange. - getBrowserRangeForNode_(startNode); - - // This happens only when startNode is a text node. - if (startOffset) { - leftRange.move('character', startOffset); - } - - - // The range movements in IE are still an approximation to the standard W3C - // behavior, and IE has its trickery when it comes to htmlText and text - // properties of the range. So we short-circuit computation whenever we can. - if (startNode == endNode && startOffset == endOffset) { - leftRange.collapse(true); - return leftRange; - } - - // This can happen only when the startNode is an element, and there is no node - // at the given offset. We start at the last point inside the startNode in - // that case. - if (collapse) { - leftRange.collapse(false); - } - - // Create a range that ends at the right position. - collapse = false; - if (endNode.nodeType == goog.dom.NodeType.ELEMENT) { - if (endOffset > endNode.childNodes.length) { - goog.dom.browserrange.IeRange.logger_.severe( - 'Cannot have endOffset > endNode child count'); - } - child = endNode.childNodes[endOffset]; - endNode = child || endNode.lastChild || endNode; - endOffset = 0; - collapse = !child; - } - var rightRange = goog.dom.browserrange.IeRange. - getBrowserRangeForNode_(endNode); - rightRange.collapse(!collapse); - if (endOffset) { - rightRange.moveEnd('character', endOffset); - } - - // Merge and return. - leftRange.setEndPoint('EndToEnd', rightRange); - return leftRange; -}; - - -/** - * Create a range object that selects the given node's text. - * @param {Node} node The node to select. - * @return {goog.dom.browserrange.IeRange} An IE range wrapper object. - */ -goog.dom.browserrange.IeRange.createFromNodeContents = function(node) { - var range = new goog.dom.browserrange.IeRange( - goog.dom.browserrange.IeRange.getBrowserRangeForNode_(node), - goog.dom.getOwnerDocument(node)); - - if (!goog.dom.browserrange.canContainRangeEndpoint(node)) { - range.startNode_ = range.endNode_ = range.parentNode_ = node.parentNode; - range.startOffset_ = goog.array.indexOf(range.parentNode_.childNodes, node); - range.endOffset_ = range.startOffset_ + 1; - } else { - // Note(user) : Emulate the behavior of W3CRange - Go to deepest possible - // range containers on both edges. It seems W3CRange did this to match the - // IE behavior, and now it is a circle. Changing W3CRange may break clients - // in all sorts of ways. - var tempNode, leaf = node; - while ((tempNode = leaf.firstChild) && - goog.dom.browserrange.canContainRangeEndpoint(tempNode)) { - leaf = tempNode; - } - range.startNode_ = leaf; - range.startOffset_ = 0; - - leaf = node; - while ((tempNode = leaf.lastChild) && - goog.dom.browserrange.canContainRangeEndpoint(tempNode)) { - leaf = tempNode; - } - range.endNode_ = leaf; - range.endOffset_ = leaf.nodeType == goog.dom.NodeType.ELEMENT ? - leaf.childNodes.length : leaf.length; - range.parentNode_ = node; - } - return range; -}; - - -/** - * Static method that returns the proper type of browser range. - * @param {Node} startNode The node to start with. - * @param {number} startOffset The offset within the start node. - * @param {Node} endNode The node to end with. - * @param {number} endOffset The offset within the end node. - * @return {goog.dom.browserrange.AbstractRange} A wrapper object. - */ -goog.dom.browserrange.IeRange.createFromNodes = function(startNode, - startOffset, endNode, endOffset) { - var range = new goog.dom.browserrange.IeRange( - goog.dom.browserrange.IeRange.getBrowserRangeForNodes_(startNode, - startOffset, endNode, endOffset), - goog.dom.getOwnerDocument(startNode)); - range.startNode_ = startNode; - range.startOffset_ = startOffset; - range.endNode_ = endNode; - range.endOffset_ = endOffset; - return range; -}; - - -// Even though goog.dom.TextRange does similar caching to below, keeping these -// caches allows for better performance in the get*Offset methods. - - -/** - * Lazy cache of the node containing the entire selection. - * @type {Node} - * @private - */ -goog.dom.browserrange.IeRange.prototype.parentNode_ = null; - - -/** - * Lazy cache of the node containing the start of the selection. - * @type {Node} - * @private - */ -goog.dom.browserrange.IeRange.prototype.startNode_ = null; - - -/** - * Lazy cache of the node containing the end of the selection. - * @type {Node} - * @private - */ -goog.dom.browserrange.IeRange.prototype.endNode_ = null; - - -/** - * Lazy cache of the offset in startNode_ where this range starts. - * @type {number} - * @private - */ -goog.dom.browserrange.IeRange.prototype.startOffset_ = -1; - - -/** - * Lazy cache of the offset in endNode_ where this range ends. - * @type {number} - * @private - */ -goog.dom.browserrange.IeRange.prototype.endOffset_ = -1; - - -/** - * @return {goog.dom.browserrange.IeRange} A clone of this range. - * @override - */ -goog.dom.browserrange.IeRange.prototype.clone = function() { - var range = new goog.dom.browserrange.IeRange( - this.range_.duplicate(), this.doc_); - range.parentNode_ = this.parentNode_; - range.startNode_ = this.startNode_; - range.endNode_ = this.endNode_; - return range; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getBrowserRange = function() { - return this.range_; -}; - - -/** - * Clears the cached values for containers. - * @private - */ -goog.dom.browserrange.IeRange.prototype.clearCachedValues_ = function() { - this.parentNode_ = this.startNode_ = this.endNode_ = null; - this.startOffset_ = this.endOffset_ = -1; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getContainer = function() { - if (!this.parentNode_) { - var selectText = this.range_.text; - - // If the selection ends with spaces, we need to remove these to get the - // parent container of only the real contents. This is to get around IE's - // inconsistency where it selects the spaces after a word when you double - // click, but leaves out the spaces during execCommands. - var range = this.range_.duplicate(); - // We can't use goog.string.trimRight, as that will remove other whitespace - // too. - var rightTrimmedSelectText = selectText.replace(/ +$/, ''); - var numSpacesAtEnd = selectText.length - rightTrimmedSelectText.length; - if (numSpacesAtEnd) { - range.moveEnd('character', -numSpacesAtEnd); - } - - // Get the parent node. This should be the end, but alas, it is not. - var parent = range.parentElement(); - - var htmlText = range.htmlText; - var htmlTextLen = goog.string.stripNewlines(htmlText).length; - if (this.isCollapsed() && htmlTextLen > 0) { - return (this.parentNode_ = parent); - } - - // Deal with selection bug where IE thinks one of the selection's children - // is actually the selection's parent. Relies on the assumption that the - // HTML text of the parent container is longer than the length of the - // selection's HTML text. - - // Also note IE will sometimes insert \r and \n whitespace, which should be - // disregarded. Otherwise the loop may run too long and return wrong parent - while (htmlTextLen > goog.string.stripNewlines(parent.outerHTML).length) { - parent = parent.parentNode; - } - - // Deal with IE's selecting the outer tags when you double click - // If the innerText is the same, then we just want the inner node - while (parent.childNodes.length == 1 && - parent.innerText == goog.dom.browserrange.IeRange.getNodeText_( - parent.firstChild)) { - // A container should be an element which can have children or a text - // node. Elements like IMG, BR, etc. can not be containers. - if (!goog.dom.browserrange.canContainRangeEndpoint(parent.firstChild)) { - break; - } - parent = parent.firstChild; - } - - // If the selection is empty, we may need to do extra work to position it - // properly. - if (selectText.length == 0) { - parent = this.findDeepestContainer_(parent); - } - - this.parentNode_ = parent; - } - - return this.parentNode_; -}; - - -/** - * Helper method to find the deepest parent for this range, starting - * the search from {@code node}, which must contain the range. - * @param {Node} node The node to start the search from. - * @return {Node} The deepest parent for this range. - * @private - */ -goog.dom.browserrange.IeRange.prototype.findDeepestContainer_ = function(node) { - var childNodes = node.childNodes; - for (var i = 0, len = childNodes.length; i < len; i++) { - var child = childNodes[i]; - - if (goog.dom.browserrange.canContainRangeEndpoint(child)) { - var childRange = - goog.dom.browserrange.IeRange.getBrowserRangeForNode_(child); - var start = goog.dom.RangeEndpoint.START; - var end = goog.dom.RangeEndpoint.END; - - // There are two types of erratic nodes where the range over node has - // different htmlText than the node's outerHTML. - // Case 1 - A node with magic child. In this case : - // nodeRange.htmlText shows ('<p> </p>), while - // node.outerHTML doesn't show the magic node (<p></p>). - // Case 2 - Empty span. In this case : - // node.outerHTML shows '<span></span>' - // node.htmlText is just empty string ''. - var isChildRangeErratic = (childRange.htmlText != child.outerHTML); - - // Moreover the inRange comparison fails only when the - var isNativeInRangeErratic = this.isCollapsed() && isChildRangeErratic; - - // In case 2 mentioned above, childRange is also collapsed. So we need to - // compare start of this range with both start and end of child range. - var inChildRange = isNativeInRangeErratic ? - (this.compareBrowserRangeEndpoints(childRange, start, start) >= 0 && - this.compareBrowserRangeEndpoints(childRange, start, end) <= 0) : - this.range_.inRange(childRange); - if (inChildRange) { - return this.findDeepestContainer_(child); - } - } - } - - return node; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getStartNode = function() { - if (!this.startNode_) { - this.startNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.START); - if (this.isCollapsed()) { - this.endNode_ = this.startNode_; - } - } - return this.startNode_; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getStartOffset = function() { - if (this.startOffset_ < 0) { - this.startOffset_ = this.getOffset_(goog.dom.RangeEndpoint.START); - if (this.isCollapsed()) { - this.endOffset_ = this.startOffset_; - } - } - return this.startOffset_; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getEndNode = function() { - if (this.isCollapsed()) { - return this.getStartNode(); - } - if (!this.endNode_) { - this.endNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.END); - } - return this.endNode_; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getEndOffset = function() { - if (this.isCollapsed()) { - return this.getStartOffset(); - } - if (this.endOffset_ < 0) { - this.endOffset_ = this.getOffset_(goog.dom.RangeEndpoint.END); - if (this.isCollapsed()) { - this.startOffset_ = this.endOffset_; - } - } - return this.endOffset_; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.compareBrowserRangeEndpoints = function( - range, thisEndpoint, otherEndpoint) { - return this.range_.compareEndPoints( - (thisEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End') + - 'To' + - (otherEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End'), - range); -}; - - -/** - * Recurses to find the correct node for the given endpoint. - * @param {goog.dom.RangeEndpoint} endpoint The endpoint to get the node for. - * @param {Node=} opt_node Optional node to start the search from. - * @return {Node} The deepest node containing the endpoint. - * @private - */ -goog.dom.browserrange.IeRange.prototype.getEndpointNode_ = function(endpoint, - opt_node) { - - /** @type {Node} */ - var node = opt_node || this.getContainer(); - - // If we're at a leaf in the DOM, we're done. - if (!node || !node.firstChild) { - return node; - } - - var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END; - var isStartEndpoint = endpoint == start; - - // Find the first/last child that overlaps the selection. - // NOTE(user) : One of the children can be the magic node. This - // node will have only nodeType property as valid and accessible. All other - // dom related properties like ownerDocument, parentNode, nextSibling etc - // cause error when accessed. Therefore use the for-loop on childNodes to - // iterate. - for (var j = 0, length = node.childNodes.length; j < length; j++) { - var i = isStartEndpoint ? j : length - j - 1; - var child = node.childNodes[i]; - var childRange; - try { - childRange = goog.dom.browserrange.createRangeFromNodeContents(child); - } catch (e) { - // If the child is the magic node, then the above will throw - // error. The magic node exists only when editing using keyboard, so can - // not add any unit test. - continue; - } - var ieRange = childRange.getBrowserRange(); - - // Case 1 : Finding end points when this range is collapsed. - // Note that in case of collapsed range, getEnd{Node,Offset} call - // getStart{Node,Offset}. - if (this.isCollapsed()) { - // Handle situations where caret is not in a text node. In such cases, - // the adjacent child won't be a valid range endpoint container. - if (!goog.dom.browserrange.canContainRangeEndpoint(child)) { - // The following handles a scenario like <div><BR>[caret]<BR></div>, - // where point should be (div, 1). - if (this.compareBrowserRangeEndpoints(ieRange, start, start) == 0) { - this.startOffset_ = this.endOffset_ = i; - return node; - } - } else if (childRange.containsRange(this)) { - // For collapsed range, we should invert the containsRange check with - // childRange. - return this.getEndpointNode_(endpoint, child); - } - - // Case 2 - The first child encountered to have overlap this range is - // contained entirely in this range. - } else if (this.containsRange(childRange)) { - // If it is an element which can not be a range endpoint container, the - // current child offset can be used to deduce the endpoint offset. - if (!goog.dom.browserrange.canContainRangeEndpoint(child)) { - - // Container can't be any deeper, so current node is the container. - if (isStartEndpoint) { - this.startOffset_ = i; - } else { - this.endOffset_ = i + 1; - } - return node; - } - - // If child can contain range endpoints, recurse inside this child. - return this.getEndpointNode_(endpoint, child); - - // Case 3 - Partial non-adjacency overlap. - } else if (this.compareBrowserRangeEndpoints(ieRange, start, end) < 0 && - this.compareBrowserRangeEndpoints(ieRange, end, start) > 0) { - // If this child overlaps the selection partially, recurse down to find - // the first/last child the next level down that overlaps the selection - // completely. We do not consider edge-adjacency (== 0) as overlap. - return this.getEndpointNode_(endpoint, child); - } - - } - - // None of the children of this node overlapped the selection, that means - // the selection starts/ends in this node directly. - return node; -}; - - -/** - * Compares one endpoint of this range with the endpoint of a node. - * For internal methods, we should prefer this method to containsNode. - * containsNode has a lot of false negatives when we're dealing with - * {@code <br>} tags. - * - * @param {Node} node The node to compare against. - * @param {goog.dom.RangeEndpoint} thisEndpoint The endpoint of this range - * to compare with. - * @param {goog.dom.RangeEndpoint} otherEndpoint The endpoint of the node - * to compare with. - * @return {number} 0 if the endpoints are equal, negative if this range - * endpoint comes before the other node endpoint, and positive otherwise. - * @private - */ -goog.dom.browserrange.IeRange.prototype.compareNodeEndpoints_ = - function(node, thisEndpoint, otherEndpoint) { - return this.range_.compareEndPoints( - (thisEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End') + - 'To' + - (otherEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End'), - goog.dom.browserrange.createRangeFromNodeContents(node). - getBrowserRange()); -}; - - -/** - * Returns the offset into the start/end container. - * @param {goog.dom.RangeEndpoint} endpoint The endpoint to get the offset for. - * @param {Node=} opt_container The container to get the offset relative to. - * Defaults to the value returned by getStartNode/getEndNode. - * @return {number} The offset. - * @private - */ -goog.dom.browserrange.IeRange.prototype.getOffset_ = function(endpoint, - opt_container) { - var isStartEndpoint = endpoint == goog.dom.RangeEndpoint.START; - var container = opt_container || - (isStartEndpoint ? this.getStartNode() : this.getEndNode()); - - if (container.nodeType == goog.dom.NodeType.ELEMENT) { - // Find the first/last child that overlaps the selection - var children = container.childNodes; - var len = children.length; - var edge = isStartEndpoint ? 0 : len - 1; - var sign = isStartEndpoint ? 1 : - 1; - - // We find the index in the child array of the endpoint of the selection. - for (var i = edge; i >= 0 && i < len; i += sign) { - var child = children[i]; - // Ignore the child nodes, which could be end point containers. - if (goog.dom.browserrange.canContainRangeEndpoint(child)) { - continue; - } - // Stop looping when we reach the edge of the selection. - var endPointCompare = - this.compareNodeEndpoints_(child, endpoint, endpoint); - if (endPointCompare == 0) { - return isStartEndpoint ? i : i + 1; - } - } - - // When starting from the end in an empty container, we erroneously return - // -1: fix this to return 0. - return i == -1 ? 0 : i; - } else { - // Get a temporary range object. - var range = this.range_.duplicate(); - - // Create a range that selects the entire container. - var nodeRange = goog.dom.browserrange.IeRange.getBrowserRangeForNode_( - container); - - // Now, intersect our range with the container range - this should give us - // the part of our selection that is in the container. - range.setEndPoint(isStartEndpoint ? 'EndToEnd' : 'StartToStart', nodeRange); - - var rangeLength = range.text.length; - return isStartEndpoint ? container.length - rangeLength : rangeLength; - } -}; - - -/** - * Returns the text of the given node. Uses IE specific properties. - * @param {Node} node The node to retrieve the text of. - * @return {string} The node's text. - * @private - */ -goog.dom.browserrange.IeRange.getNodeText_ = function(node) { - return node.nodeType == goog.dom.NodeType.TEXT ? - node.nodeValue : node.innerText; -}; - - -/** - * Tests whether this range is valid (i.e. whether its endpoints are still in - * the document). A range becomes invalid when, after this object was created, - * either one or both of its endpoints are removed from the document. Use of - * an invalid range can lead to runtime errors, particularly in IE. - * @return {boolean} Whether the range is valid. - */ -goog.dom.browserrange.IeRange.prototype.isRangeInDocument = function() { - var range = this.doc_.body.createTextRange(); - range.moveToElementText(this.doc_.body); - - return this.containsRange( - new goog.dom.browserrange.IeRange(range, this.doc_), true); -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.isCollapsed = function() { - // Note(user) : The earlier implementation used (range.text == ''), but this - // fails when (range.htmlText == '<br>') - // Alternative: this.range_.htmlText == ''; - return this.range_.compareEndPoints('StartToEnd', this.range_) == 0; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getText = function() { - return this.range_.text; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.getValidHtml = function() { - return this.range_.htmlText; -}; - - -// SELECTION MODIFICATION - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.select = function(opt_reverse) { - // IE doesn't support programmatic reversed selections. - this.range_.select(); -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.removeContents = function() { - // NOTE: Sometimes htmlText is non-empty, but the range is actually empty. - // TODO(gboyer): The htmlText check is probably unnecessary, but I left it in - // for paranoia. - if (!this.isCollapsed() && this.range_.htmlText) { - // Store some before-removal state. - var startNode = this.getStartNode(); - var endNode = this.getEndNode(); - var oldText = this.range_.text; - - // IE sometimes deletes nodes unrelated to the selection. This trick fixes - // that problem most of the time. Even though it looks like a no-op, it is - // somehow changing IE's internal state such that empty unrelated nodes are - // no longer deleted. - var clone = this.range_.duplicate(); - clone.moveStart('character', 1); - clone.moveStart('character', -1); - - // However, sometimes moving the start back and forth ends up changing the - // range. - // TODO(gboyer): This condition used to happen for empty ranges, but (1) - // never worked, and (2) the isCollapsed call should protect against empty - // ranges better than before. However, this is left for paranoia. - if (clone.text == oldText) { - this.range_ = clone; - } - - // Use the browser's native deletion code. - this.range_.text = ''; - this.clearCachedValues_(); - - // Unfortunately, when deleting a portion of a single text node, IE creates - // an extra text node unlike other browsers which just change the text in - // the node. We normalize for that behavior here, making IE behave like all - // the other browsers. - var newStartNode = this.getStartNode(); - var newStartOffset = this.getStartOffset(); - /** @preserveTry */ - try { - var sibling = startNode.nextSibling; - if (startNode == endNode && startNode.parentNode && - startNode.nodeType == goog.dom.NodeType.TEXT && - sibling && sibling.nodeType == goog.dom.NodeType.TEXT) { - startNode.nodeValue += sibling.nodeValue; - goog.dom.removeNode(sibling); - - // Make sure to reselect the appropriate position. - this.range_ = goog.dom.browserrange.IeRange.getBrowserRangeForNode_( - newStartNode); - this.range_.move('character', newStartOffset); - this.clearCachedValues_(); - } - } catch (e) { - // IE throws errors on orphaned nodes. - } - } -}; - - -/** - * @param {TextRange} range The range to get a dom helper for. - * @return {goog.dom.DomHelper} A dom helper for the document the range - * resides in. - * @private - */ -goog.dom.browserrange.IeRange.getDomHelper_ = function(range) { - return goog.dom.getDomHelper(range.parentElement()); -}; - - -/** - * Pastes the given element into the given range, returning the resulting - * element. - * @param {TextRange} range The range to paste into. - * @param {Element} element The node to insert a copy of. - * @param {goog.dom.DomHelper=} opt_domHelper DOM helper object for the document - * the range resides in. - * @return {Element} The resulting copy of element. - * @private - */ -goog.dom.browserrange.IeRange.pasteElement_ = function(range, element, - opt_domHelper) { - opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_( - range); - - // Make sure the node has a unique id. - var id; - var originalId = id = element.id; - if (!id) { - id = element.id = goog.string.createUniqueString(); - } - - // Insert (a clone of) the node. - range.pasteHTML(element.outerHTML); - - // Pasting the outerHTML of the modified element into the document creates - // a clone of the element argument. We want to return a reference to the - // clone, not the original. However we need to remove the temporary ID - // first. - element = opt_domHelper.getElement(id); - - // If element is null here, we failed. - if (element) { - if (!originalId) { - element.removeAttribute('id'); - } - } - - return element; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.surroundContents = function(element) { - // Make sure the element is detached from the document. - goog.dom.removeNode(element); - - // IE more or less guarantees that range.htmlText is well-formed & valid. - element.innerHTML = this.range_.htmlText; - element = goog.dom.browserrange.IeRange.pasteElement_(this.range_, element); - - // If element is null here, we failed. - if (element) { - this.range_.moveToElementText(element); - } - - this.clearCachedValues_(); - - return element; -}; - - -/** - * Internal handler for inserting a node. - * @param {TextRange} clone A clone of this range's browser range object. - * @param {Node} node The node to insert. - * @param {boolean} before Whether to insert the node before or after the range. - * @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use. - * @return {Node} The resulting copy of node. - * @private - */ -goog.dom.browserrange.IeRange.insertNode_ = function(clone, node, - before, opt_domHelper) { - // Get a DOM helper. - opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_( - clone); - - // If it's not an element, wrap it in one. - var isNonElement; - if (node.nodeType != goog.dom.NodeType.ELEMENT) { - isNonElement = true; - node = opt_domHelper.createDom(goog.dom.TagName.DIV, null, node); - } - - clone.collapse(before); - node = goog.dom.browserrange.IeRange.pasteElement_(clone, - /** @type {Element} */ (node), opt_domHelper); - - // If we didn't want an element, unwrap the element and return the node. - if (isNonElement) { - // pasteElement_() may have returned a copy of the wrapper div, and the - // node it wraps could also be a new copy. So we must extract that new - // node from the new wrapper. - var newNonElement = node.firstChild; - opt_domHelper.flattenElement(node); - node = newNonElement; - } - - return node; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.insertNode = function(node, before) { - var output = goog.dom.browserrange.IeRange.insertNode_( - this.range_.duplicate(), node, before); - this.clearCachedValues_(); - return output; -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.surroundWithNodes = function( - startNode, endNode) { - var clone1 = this.range_.duplicate(); - var clone2 = this.range_.duplicate(); - goog.dom.browserrange.IeRange.insertNode_(clone1, startNode, true); - goog.dom.browserrange.IeRange.insertNode_(clone2, endNode, false); - - this.clearCachedValues_(); -}; - - -/** @override */ -goog.dom.browserrange.IeRange.prototype.collapse = function(toStart) { - this.range_.collapse(toStart); - - if (toStart) { - this.endNode_ = this.startNode_; - this.endOffset_ = this.startOffset_; - } else { - this.startNode_ = this.endNode_; - this.startOffset_ = this.endOffset_; - } -}; |