123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- // 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 W3C spec following range wrapper.
- *
- * DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
- *
- * @author robbyw@google.com (Robby Walker)
- */
- goog.provide('goog.dom.browserrange.W3cRange');
- goog.require('goog.array');
- goog.require('goog.dom');
- goog.require('goog.dom.NodeType');
- goog.require('goog.dom.RangeEndpoint');
- goog.require('goog.dom.TagName');
- goog.require('goog.dom.browserrange.AbstractRange');
- goog.require('goog.string');
- goog.require('goog.userAgent');
- /**
- * The constructor for W3C specific browser ranges.
- * @param {Range} range The range object.
- * @constructor
- * @extends {goog.dom.browserrange.AbstractRange}
- */
- goog.dom.browserrange.W3cRange = function(range) {
- this.range_ = range;
- };
- goog.inherits(
- goog.dom.browserrange.W3cRange, goog.dom.browserrange.AbstractRange);
- /**
- * Returns a browser range spanning the given node's contents.
- * @param {Node} node The node to select.
- * @return {!Range} A browser range spanning the node's contents.
- * @protected
- */
- goog.dom.browserrange.W3cRange.getBrowserRangeForNode = function(node) {
- var nodeRange = goog.dom.getOwnerDocument(node).createRange();
- if (node.nodeType == goog.dom.NodeType.TEXT) {
- nodeRange.setStart(node, 0);
- nodeRange.setEnd(node, node.length);
- } else {
- /** @suppress {missingRequire} */
- if (!goog.dom.browserrange.canContainRangeEndpoint(node)) {
- var rangeParent = node.parentNode;
- var rangeStartOffset = goog.array.indexOf(rangeParent.childNodes, node);
- nodeRange.setStart(rangeParent, rangeStartOffset);
- nodeRange.setEnd(rangeParent, rangeStartOffset + 1);
- } else {
- var tempNode, leaf = node;
- while ((tempNode = leaf.firstChild) &&
- /** @suppress {missingRequire} */
- goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
- leaf = tempNode;
- }
- nodeRange.setStart(leaf, 0);
- leaf = node;
- /** @suppress {missingRequire} Circular dep with browserrange */
- while ((tempNode = leaf.lastChild) &&
- goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
- leaf = tempNode;
- }
- nodeRange.setEnd(
- leaf, leaf.nodeType == goog.dom.NodeType.ELEMENT ?
- leaf.childNodes.length :
- leaf.length);
- }
- }
- return nodeRange;
- };
- /**
- * Returns a browser range spanning the given nodes.
- * @param {Node} startNode The node to start with - should not be a BR.
- * @param {number} startOffset The offset within the start node.
- * @param {Node} endNode The node to end with - should not be a BR.
- * @param {number} endOffset The offset within the end node.
- * @return {!Range} A browser range spanning the node's contents.
- * @protected
- */
- goog.dom.browserrange.W3cRange.getBrowserRangeForNodes = function(
- startNode, startOffset, endNode, endOffset) {
- // Create and return the range.
- var nodeRange = goog.dom.getOwnerDocument(startNode).createRange();
- nodeRange.setStart(startNode, startOffset);
- nodeRange.setEnd(endNode, endOffset);
- return nodeRange;
- };
- /**
- * Creates a range object that selects the given node's text.
- * @param {Node} node The node to select.
- * @return {!goog.dom.browserrange.W3cRange} A Gecko range wrapper object.
- */
- goog.dom.browserrange.W3cRange.createFromNodeContents = function(node) {
- return new goog.dom.browserrange.W3cRange(
- goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
- };
- /**
- * Creates a range object that selects between 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 {!goog.dom.browserrange.W3cRange} A wrapper object.
- */
- goog.dom.browserrange.W3cRange.createFromNodes = function(
- startNode, startOffset, endNode, endOffset) {
- return new goog.dom.browserrange.W3cRange(
- goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(
- startNode, startOffset, endNode, endOffset));
- };
- /**
- * @return {!goog.dom.browserrange.W3cRange} A clone of this range.
- * @override
- */
- goog.dom.browserrange.W3cRange.prototype.clone = function() {
- return new this.constructor(this.range_.cloneRange());
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getBrowserRange = function() {
- return this.range_;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getContainer = function() {
- return this.range_.commonAncestorContainer;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getStartNode = function() {
- return this.range_.startContainer;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getStartOffset = function() {
- return this.range_.startOffset;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getEndNode = function() {
- return this.range_.endContainer;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getEndOffset = function() {
- return this.range_.endOffset;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.compareBrowserRangeEndpoints =
- function(range, thisEndpoint, otherEndpoint) {
- return this.range_.compareBoundaryPoints(
- otherEndpoint == goog.dom.RangeEndpoint.START ?
- (thisEndpoint == goog.dom.RangeEndpoint.START ?
- goog.global['Range'].START_TO_START :
- goog.global['Range'].START_TO_END) :
- (thisEndpoint == goog.dom.RangeEndpoint.START ?
- goog.global['Range'].END_TO_START :
- goog.global['Range'].END_TO_END),
- /** @type {Range} */ (range));
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.isCollapsed = function() {
- return this.range_.collapsed;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getText = function() {
- return this.range_.toString();
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.getValidHtml = function() {
- var div = goog.dom.getDomHelper(this.range_.startContainer)
- .createDom(goog.dom.TagName.DIV);
- div.appendChild(this.range_.cloneContents());
- var result = div.innerHTML;
- if (goog.string.startsWith(result, '<') ||
- !this.isCollapsed() && !goog.string.contains(result, '<')) {
- // We attempt to mimic IE, which returns no containing element when a
- // only text nodes are selected, does return the containing element when
- // the selection is empty, and does return the element when multiple nodes
- // are selected.
- return result;
- }
- var container = this.getContainer();
- container = container.nodeType == goog.dom.NodeType.ELEMENT ?
- container :
- container.parentNode;
- var html = goog.dom.getOuterHtml(
- /** @type {!Element} */ (container.cloneNode(false)));
- return html.replace('>', '>' + result);
- };
- // SELECTION MODIFICATION
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.select = function(reverse) {
- var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
- this.selectInternal(win.getSelection(), reverse);
- };
- /**
- * Select this range.
- * @param {Selection} selection Browser selection object.
- * @param {*} reverse Whether to select this range in reverse.
- * @protected
- */
- goog.dom.browserrange.W3cRange.prototype.selectInternal = function(
- selection, reverse) {
- // Browser-specific tricks are needed to create reversed selections
- // programatically. For this generic W3C codepath, ignore the reverse
- // parameter.
- selection.removeAllRanges();
- selection.addRange(this.range_);
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.removeContents = function() {
- var range = this.range_;
- range.extractContents();
- if (range.startContainer.hasChildNodes()) {
- // Remove any now empty nodes surrounding the extracted contents.
- var rangeStartContainer =
- range.startContainer.childNodes[range.startOffset];
- if (rangeStartContainer) {
- var rangePrevious = rangeStartContainer.previousSibling;
- if (goog.dom.getRawTextContent(rangeStartContainer) == '') {
- goog.dom.removeNode(rangeStartContainer);
- }
- if (rangePrevious && goog.dom.getRawTextContent(rangePrevious) == '') {
- goog.dom.removeNode(rangePrevious);
- }
- }
- }
- if (goog.userAgent.EDGE_OR_IE) {
- // Unfortunately, when deleting a portion of a single text node, IE creates
- // an extra text node instead of modifying the nodeValue of the start node.
- // We normalize for that behavior here, similar to code in
- // goog.dom.browserrange.IeRange#removeContents
- // See https://connect.microsoft.com/IE/feedback/details/746591
- var startNode = this.getStartNode();
- var startOffset = this.getStartOffset();
- var endNode = this.getEndNode();
- var endOffset = this.getEndOffset();
- 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);
- // Modifying the node value clears the range offsets. Reselect the
- // position in the modified start node.
- range.setStart(startNode, startOffset);
- range.setEnd(endNode, endOffset);
- }
- }
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.surroundContents = function(element) {
- this.range_.surroundContents(element);
- return element;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.insertNode = function(node, before) {
- var range = this.range_.cloneRange();
- range.collapse(before);
- range.insertNode(node);
- range.detach();
- return node;
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.surroundWithNodes = function(
- startNode, endNode) {
- var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
- /** @suppress {missingRequire} */
- var selectionRange = goog.dom.Range.createFromWindow(win);
- if (selectionRange) {
- var sNode = selectionRange.getStartNode();
- var eNode = selectionRange.getEndNode();
- var sOffset = selectionRange.getStartOffset();
- var eOffset = selectionRange.getEndOffset();
- }
- var clone1 = this.range_.cloneRange();
- var clone2 = this.range_.cloneRange();
- clone1.collapse(false);
- clone2.collapse(true);
- clone1.insertNode(endNode);
- clone2.insertNode(startNode);
- clone1.detach();
- clone2.detach();
- if (selectionRange) {
- // There are 4 ways that surroundWithNodes can wreck the saved
- // selection object. All of them happen when an inserted node splits
- // a text node, and one of the end points of the selection was in the
- // latter half of that text node.
- //
- // Clients of this library should use saveUsingCarets to avoid this
- // problem. Unfortunately, saveUsingCarets uses this method, so that's
- // not really an option for us. :( We just recompute the offsets.
- var isInsertedNode = function(n) { return n == startNode || n == endNode; };
- if (sNode.nodeType == goog.dom.NodeType.TEXT) {
- while (sOffset > sNode.length) {
- sOffset -= sNode.length;
- do {
- sNode = sNode.nextSibling;
- } while (isInsertedNode(sNode));
- }
- }
- if (eNode.nodeType == goog.dom.NodeType.TEXT) {
- while (eOffset > eNode.length) {
- eOffset -= eNode.length;
- do {
- eNode = eNode.nextSibling;
- } while (isInsertedNode(eNode));
- }
- }
- /** @suppress {missingRequire} */
- goog.dom.Range
- .createFromNodes(
- sNode, /** @type {number} */ (sOffset), eNode,
- /** @type {number} */ (eOffset))
- .select();
- }
- };
- /** @override */
- goog.dom.browserrange.W3cRange.prototype.collapse = function(toStart) {
- this.range_.collapse(toStart);
- };
|