range.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Utilties for working with ranges.
  16. *
  17. * @author nicksantos@google.com (Nick Santos)
  18. */
  19. goog.provide('goog.editor.range');
  20. goog.provide('goog.editor.range.Point');
  21. goog.require('goog.array');
  22. goog.require('goog.dom');
  23. goog.require('goog.dom.NodeType');
  24. goog.require('goog.dom.Range');
  25. goog.require('goog.dom.RangeEndpoint');
  26. goog.require('goog.dom.SavedCaretRange');
  27. goog.require('goog.editor.node');
  28. goog.require('goog.editor.style');
  29. goog.require('goog.iter');
  30. goog.require('goog.userAgent');
  31. /**
  32. * Given a range and an element, create a narrower range that is limited to the
  33. * boundaries of the element. If the range starts (or ends) outside the
  34. * element, the narrowed range's start point (or end point) will be the
  35. * leftmost (or rightmost) leaf of the element.
  36. * @param {goog.dom.AbstractRange} range The range.
  37. * @param {Element} el The element to limit the range to.
  38. * @return {goog.dom.AbstractRange} A new narrowed range, or null if the
  39. * element does not contain any part of the given range.
  40. */
  41. goog.editor.range.narrow = function(range, el) {
  42. var startContainer = range.getStartNode();
  43. var endContainer = range.getEndNode();
  44. if (startContainer && endContainer) {
  45. var isElement = function(node) { return node == el; };
  46. var hasStart = goog.dom.getAncestor(startContainer, isElement, true);
  47. var hasEnd = goog.dom.getAncestor(endContainer, isElement, true);
  48. if (hasStart && hasEnd) {
  49. // The range is contained entirely within this element.
  50. return range.clone();
  51. } else if (hasStart) {
  52. // The range starts inside the element, but ends outside it.
  53. var leaf = goog.editor.node.getRightMostLeaf(el);
  54. return goog.dom.Range.createFromNodes(
  55. range.getStartNode(), range.getStartOffset(), leaf,
  56. goog.editor.node.getLength(leaf));
  57. } else if (hasEnd) {
  58. // The range starts outside the element, but ends inside it.
  59. return goog.dom.Range.createFromNodes(
  60. goog.editor.node.getLeftMostLeaf(el), 0, range.getEndNode(),
  61. range.getEndOffset());
  62. }
  63. }
  64. // The selection starts and ends outside the element.
  65. return null;
  66. };
  67. /**
  68. * Given a range, expand the range to include outer tags if the full contents of
  69. * those tags are entirely selected. This essentially changes the dom position,
  70. * but not the visible position of the range.
  71. * Ex. <code><li>foo</li></code> if "foo" is selected, instead of returning
  72. * start and end nodes as the foo text node, return the li.
  73. * @param {goog.dom.AbstractRange} range The range.
  74. * @param {Node=} opt_stopNode Optional node to stop expanding past.
  75. * @return {!goog.dom.AbstractRange} The expanded range.
  76. */
  77. goog.editor.range.expand = function(range, opt_stopNode) {
  78. // Expand the start out to the common container.
  79. var expandedRange = goog.editor.range.expandEndPointToContainer_(
  80. range, goog.dom.RangeEndpoint.START, opt_stopNode);
  81. // Expand the end out to the common container.
  82. expandedRange = goog.editor.range.expandEndPointToContainer_(
  83. expandedRange, goog.dom.RangeEndpoint.END, opt_stopNode);
  84. var startNode = expandedRange.getStartNode();
  85. var endNode = expandedRange.getEndNode();
  86. var startOffset = expandedRange.getStartOffset();
  87. var endOffset = expandedRange.getEndOffset();
  88. // If we have reached a common container, now expand out.
  89. if (startNode == endNode) {
  90. while (endNode != opt_stopNode && startOffset == 0 &&
  91. endOffset == goog.editor.node.getLength(endNode)) {
  92. // Select the parent instead.
  93. var parentNode = endNode.parentNode;
  94. startOffset = goog.array.indexOf(parentNode.childNodes, endNode);
  95. endOffset = startOffset + 1;
  96. endNode = parentNode;
  97. }
  98. startNode = endNode;
  99. }
  100. return goog.dom.Range.createFromNodes(
  101. startNode, startOffset, endNode, endOffset);
  102. };
  103. /**
  104. * Given a range, expands the start or end points as far out towards the
  105. * range's common container (or stopNode, if provided) as possible, while
  106. * perserving the same visible position.
  107. *
  108. * @param {goog.dom.AbstractRange} range The range to expand.
  109. * @param {goog.dom.RangeEndpoint} endpoint The endpoint to expand.
  110. * @param {Node=} opt_stopNode Optional node to stop expanding past.
  111. * @return {!goog.dom.AbstractRange} The expanded range.
  112. * @private
  113. */
  114. goog.editor.range.expandEndPointToContainer_ = function(
  115. range, endpoint, opt_stopNode) {
  116. var expandStart = endpoint == goog.dom.RangeEndpoint.START;
  117. var node = expandStart ? range.getStartNode() : range.getEndNode();
  118. var offset = expandStart ? range.getStartOffset() : range.getEndOffset();
  119. var container = range.getContainerElement();
  120. // Expand the node out until we reach the container or the stop node.
  121. while (node != container && node != opt_stopNode) {
  122. // It is only valid to expand the start if we are at the start of a node
  123. // (offset 0) or expand the end if we are at the end of a node
  124. // (offset length).
  125. if (expandStart && offset != 0 ||
  126. !expandStart && offset != goog.editor.node.getLength(node)) {
  127. break;
  128. }
  129. var parentNode = node.parentNode;
  130. var index = goog.array.indexOf(parentNode.childNodes, node);
  131. offset = expandStart ? index : index + 1;
  132. node = parentNode;
  133. }
  134. return goog.dom.Range.createFromNodes(
  135. expandStart ? node : range.getStartNode(),
  136. expandStart ? offset : range.getStartOffset(),
  137. expandStart ? range.getEndNode() : node,
  138. expandStart ? range.getEndOffset() : offset);
  139. };
  140. /**
  141. * Cause the window's selection to be the start of this node.
  142. * @param {Node} node The node to select the start of.
  143. */
  144. goog.editor.range.selectNodeStart = function(node) {
  145. goog.dom.Range.createCaret(goog.editor.node.getLeftMostLeaf(node), 0)
  146. .select();
  147. };
  148. /**
  149. * Position the cursor immediately to the left or right of "node".
  150. * In Firefox, the selection parent is outside of "node", so the cursor can
  151. * effectively be moved to the end of a link node, without being considered
  152. * inside of it.
  153. * Note: This does not always work in WebKit. In particular, if you try to
  154. * place a cursor to the right of a link, typing still puts you in the link.
  155. * Bug: http://bugs.webkit.org/show_bug.cgi?id=17697
  156. * @param {Node} node The node to position the cursor relative to.
  157. * @param {boolean} toLeft True to place it to the left, false to the right.
  158. * @return {!goog.dom.AbstractRange} The newly selected range.
  159. */
  160. goog.editor.range.placeCursorNextTo = function(node, toLeft) {
  161. var parent = node.parentNode;
  162. var offset = goog.array.indexOf(parent.childNodes, node) + (toLeft ? 0 : 1);
  163. var point =
  164. goog.editor.range.Point.createDeepestPoint(parent, offset, toLeft, true);
  165. var range = goog.dom.Range.createCaret(point.node, point.offset);
  166. range.select();
  167. return range;
  168. };
  169. /**
  170. * Normalizes the node, preserving the selection of the document.
  171. *
  172. * May also normalize things outside the node, if it is more efficient to do so.
  173. *
  174. * @param {Node} node The node to normalize.
  175. */
  176. goog.editor.range.selectionPreservingNormalize = function(node) {
  177. var doc = goog.dom.getOwnerDocument(node);
  178. var selection = goog.dom.Range.createFromWindow(goog.dom.getWindow(doc));
  179. var normalizedRange =
  180. goog.editor.range.rangePreservingNormalize(node, selection);
  181. if (normalizedRange) {
  182. normalizedRange.select();
  183. }
  184. };
  185. /**
  186. * Manually normalizes the node in IE, since native normalize in IE causes
  187. * transient problems.
  188. * @param {Node} node The node to normalize.
  189. * @private
  190. */
  191. goog.editor.range.normalizeNodeIe_ = function(node) {
  192. var lastText = null;
  193. var child = node.firstChild;
  194. while (child) {
  195. var next = child.nextSibling;
  196. if (child.nodeType == goog.dom.NodeType.TEXT) {
  197. if (child.nodeValue == '') {
  198. node.removeChild(child);
  199. } else if (lastText) {
  200. lastText.nodeValue += child.nodeValue;
  201. node.removeChild(child);
  202. } else {
  203. lastText = child;
  204. }
  205. } else {
  206. goog.editor.range.normalizeNodeIe_(child);
  207. lastText = null;
  208. }
  209. child = next;
  210. }
  211. };
  212. /**
  213. * Normalizes the given node.
  214. * @param {Node} node The node to normalize.
  215. */
  216. goog.editor.range.normalizeNode = function(node) {
  217. if (goog.userAgent.IE) {
  218. goog.editor.range.normalizeNodeIe_(node);
  219. } else {
  220. node.normalize();
  221. }
  222. };
  223. /**
  224. * Normalizes the node, preserving a range of the document.
  225. *
  226. * May also normalize things outside the node, if it is more efficient to do so.
  227. *
  228. * @param {Node} node The node to normalize.
  229. * @param {goog.dom.AbstractRange?} range The range to normalize.
  230. * @return {goog.dom.AbstractRange?} The range, adjusted for normalization.
  231. */
  232. goog.editor.range.rangePreservingNormalize = function(node, range) {
  233. if (range) {
  234. var rangeFactory = goog.editor.range.normalize(range);
  235. // WebKit has broken selection affinity, so carets tend to jump out of the
  236. // beginning of inline elements. This means that if we're doing the
  237. // normalize as the result of a range that will later become the selection,
  238. // we might not normalize something in the range after it is read back from
  239. // the selection. We can't just normalize the parentNode here because WebKit
  240. // can move the selection range out of multiple inline parents.
  241. var container = goog.editor.style.getContainer(range.getContainerElement());
  242. }
  243. if (container) {
  244. goog.editor.range.normalizeNode(
  245. goog.dom.findCommonAncestor(container, node));
  246. } else if (node) {
  247. goog.editor.range.normalizeNode(node);
  248. }
  249. if (rangeFactory) {
  250. return rangeFactory();
  251. } else {
  252. return null;
  253. }
  254. };
  255. /**
  256. * Get the deepest point in the DOM that's equivalent to the endpoint of the
  257. * given range.
  258. *
  259. * @param {goog.dom.AbstractRange} range A range.
  260. * @param {boolean} atStart True for the start point, false for the end point.
  261. * @return {!goog.editor.range.Point} The end point, expressed as a node
  262. * and an offset.
  263. */
  264. goog.editor.range.getDeepEndPoint = function(range, atStart) {
  265. return atStart ?
  266. goog.editor.range.Point.createDeepestPoint(
  267. range.getStartNode(), range.getStartOffset()) :
  268. goog.editor.range.Point.createDeepestPoint(
  269. range.getEndNode(), range.getEndOffset());
  270. };
  271. /**
  272. * Given a range in the current DOM, create a factory for a range that
  273. * represents the same selection in a normalized DOM. The factory function
  274. * should be invoked after the DOM is normalized.
  275. *
  276. * All browsers do a bad job preserving ranges across DOM normalization.
  277. * The issue is best described in this 5-year-old bug report:
  278. * https://bugzilla.mozilla.org/show_bug.cgi?id=191864
  279. * For most applications, this isn't a problem. The browsers do a good job
  280. * handling un-normalized text, so there's usually no reason to normalize.
  281. *
  282. * The exception to this rule is the rich text editing commands
  283. * execCommand and queryCommandValue, which will fail often if there are
  284. * un-normalized text nodes.
  285. *
  286. * The factory function creates new ranges so that we can normalize the DOM
  287. * without problems. It must be created before any normalization happens,
  288. * and invoked after normalization happens.
  289. *
  290. * @param {goog.dom.AbstractRange} range The range to normalize. It may
  291. * become invalid after body.normalize() is called.
  292. * @return {function(): goog.dom.AbstractRange} A factory for a normalized
  293. * range. Should be called after body.normalize() is called.
  294. */
  295. goog.editor.range.normalize = function(range) {
  296. var isReversed = range.isReversed();
  297. var anchorPoint = goog.editor.range.normalizePoint_(
  298. goog.editor.range.getDeepEndPoint(range, !isReversed));
  299. var anchorParent = anchorPoint.getParentPoint();
  300. var anchorPreviousSibling = anchorPoint.node.previousSibling;
  301. if (anchorPoint.node.nodeType == goog.dom.NodeType.TEXT) {
  302. anchorPoint.node = null;
  303. }
  304. var focusPoint = goog.editor.range.normalizePoint_(
  305. goog.editor.range.getDeepEndPoint(range, isReversed));
  306. var focusParent = focusPoint.getParentPoint();
  307. var focusPreviousSibling = focusPoint.node.previousSibling;
  308. if (focusPoint.node.nodeType == goog.dom.NodeType.TEXT) {
  309. focusPoint.node = null;
  310. }
  311. return function() {
  312. if (!anchorPoint.node && anchorPreviousSibling) {
  313. // If anchorPoint.node was previously an empty text node with no siblings,
  314. // anchorPreviousSibling may not have a nextSibling since that node will
  315. // no longer exist. Do our best and point to the end of the previous
  316. // element.
  317. anchorPoint.node = anchorPreviousSibling.nextSibling;
  318. if (!anchorPoint.node) {
  319. anchorPoint =
  320. goog.editor.range.Point.getPointAtEndOfNode(anchorPreviousSibling);
  321. }
  322. }
  323. if (!focusPoint.node && focusPreviousSibling) {
  324. // If focusPoint.node was previously an empty text node with no siblings,
  325. // focusPreviousSibling may not have a nextSibling since that node will no
  326. // longer exist. Do our best and point to the end of the previous
  327. // element.
  328. focusPoint.node = focusPreviousSibling.nextSibling;
  329. if (!focusPoint.node) {
  330. focusPoint =
  331. goog.editor.range.Point.getPointAtEndOfNode(focusPreviousSibling);
  332. }
  333. }
  334. return goog.dom.Range.createFromNodes(
  335. anchorPoint.node || anchorParent.node.firstChild || anchorParent.node,
  336. anchorPoint.offset,
  337. focusPoint.node || focusParent.node.firstChild || focusParent.node,
  338. focusPoint.offset);
  339. };
  340. };
  341. /**
  342. * Given a point in the current DOM, adjust it to represent the same point in
  343. * a normalized DOM.
  344. *
  345. * See the comments on goog.editor.range.normalize for more context.
  346. *
  347. * @param {goog.editor.range.Point} point A point in the document.
  348. * @return {!goog.editor.range.Point} The same point, for easy chaining.
  349. * @private
  350. */
  351. goog.editor.range.normalizePoint_ = function(point) {
  352. var previous;
  353. if (point.node.nodeType == goog.dom.NodeType.TEXT) {
  354. // If the cursor position is in a text node,
  355. // look at all the previous text siblings of the text node,
  356. // and set the offset relative to the earliest text sibling.
  357. for (var current = point.node.previousSibling;
  358. current && current.nodeType == goog.dom.NodeType.TEXT;
  359. current = current.previousSibling) {
  360. point.offset += goog.editor.node.getLength(current);
  361. }
  362. previous = current;
  363. } else {
  364. previous = point.node.previousSibling;
  365. }
  366. var parent = point.node.parentNode;
  367. point.node = previous ? previous.nextSibling : parent.firstChild;
  368. return point;
  369. };
  370. /**
  371. * Checks if a range is completely inside an editable region.
  372. * @param {goog.dom.AbstractRange} range The range to test.
  373. * @return {boolean} Whether the range is completely inside an editable region.
  374. */
  375. goog.editor.range.isEditable = function(range) {
  376. var rangeContainer = range.getContainerElement();
  377. // Closure's implementation of getContainerElement() is a little too
  378. // smart in IE when exactly one element is contained in the range.
  379. // It assumes that there's a user whose intent was actually to select
  380. // all that element's children, so it returns the element itself as its
  381. // own containing element.
  382. // This little sanity check detects this condition so we can account for it.
  383. var rangeContainerIsOutsideRange =
  384. range.getStartNode() != rangeContainer.parentElement;
  385. return (rangeContainerIsOutsideRange &&
  386. goog.editor.node.isEditableContainer(rangeContainer)) ||
  387. goog.editor.node.isEditable(rangeContainer);
  388. };
  389. /**
  390. * Returns whether the given range intersects with any instance of the given
  391. * tag.
  392. * @param {goog.dom.AbstractRange} range The range to check.
  393. * @param {!goog.dom.TagName} tagName The name of the tag.
  394. * @return {boolean} Whether the given range intersects with any instance of
  395. * the given tag.
  396. */
  397. goog.editor.range.intersectsTag = function(range, tagName) {
  398. if (goog.dom.getAncestorByTagNameAndClass(
  399. range.getContainerElement(), tagName)) {
  400. return true;
  401. }
  402. return goog.iter.some(
  403. range, function(node) { return node.tagName == tagName; });
  404. };
  405. /**
  406. * One endpoint of a range, represented as a Node and and offset.
  407. * @param {Node} node The node containing the point.
  408. * @param {number} offset The offset of the point into the node.
  409. * @constructor
  410. * @final
  411. */
  412. goog.editor.range.Point = function(node, offset) {
  413. /**
  414. * The node containing the point.
  415. * @type {Node}
  416. */
  417. this.node = node;
  418. /**
  419. * The offset of the point into the node.
  420. * @type {number}
  421. */
  422. this.offset = offset;
  423. };
  424. /**
  425. * Gets the point of this point's node in the DOM.
  426. * @return {!goog.editor.range.Point} The node's point.
  427. */
  428. goog.editor.range.Point.prototype.getParentPoint = function() {
  429. var parent = this.node.parentNode;
  430. return new goog.editor.range.Point(
  431. parent, goog.array.indexOf(parent.childNodes, this.node));
  432. };
  433. /**
  434. * Construct the deepest possible point in the DOM that's equivalent
  435. * to the given point, expressed as a node and an offset.
  436. * @param {Node} node The node containing the point.
  437. * @param {number} offset The offset of the point from the node.
  438. * @param {boolean=} opt_trendLeft Notice that a (node, offset) pair may be
  439. * equivalent to more than one descendent (node, offset) pair in the DOM.
  440. * By default, we trend rightward. If this parameter is true, then we
  441. * trend leftward. The tendency to fall rightward by default is for
  442. * consistency with other range APIs (like placeCursorNextTo).
  443. * @param {boolean=} opt_stopOnChildlessElement If true, and we encounter
  444. * a Node which is an Element that cannot have children, we return a Point
  445. * based on its parent rather than that Node itself.
  446. * @return {!goog.editor.range.Point} A new point.
  447. */
  448. goog.editor.range.Point.createDeepestPoint = function(
  449. node, offset, opt_trendLeft, opt_stopOnChildlessElement) {
  450. while (node.nodeType == goog.dom.NodeType.ELEMENT) {
  451. var child = node.childNodes[offset];
  452. if (!child && !node.lastChild) {
  453. break;
  454. } else if (child) {
  455. var prevSibling = child.previousSibling;
  456. if (opt_trendLeft && prevSibling) {
  457. if (opt_stopOnChildlessElement &&
  458. goog.editor.range.Point.isTerminalElement_(prevSibling)) {
  459. break;
  460. }
  461. node = prevSibling;
  462. offset = goog.editor.node.getLength(node);
  463. } else {
  464. if (opt_stopOnChildlessElement &&
  465. goog.editor.range.Point.isTerminalElement_(child)) {
  466. break;
  467. }
  468. node = child;
  469. offset = 0;
  470. }
  471. } else {
  472. if (opt_stopOnChildlessElement &&
  473. goog.editor.range.Point.isTerminalElement_(node.lastChild)) {
  474. break;
  475. }
  476. node = node.lastChild;
  477. offset = goog.editor.node.getLength(node);
  478. }
  479. }
  480. return new goog.editor.range.Point(node, offset);
  481. };
  482. /**
  483. * Return true if the specified node is an Element that is not expected to have
  484. * children. The createDeepestPoint() method should not traverse into
  485. * such elements.
  486. * @param {Node} node .
  487. * @return {boolean} True if the node is an Element that does not contain
  488. * child nodes (e.g. BR, IMG).
  489. * @private
  490. */
  491. goog.editor.range.Point.isTerminalElement_ = function(node) {
  492. return (
  493. node.nodeType == goog.dom.NodeType.ELEMENT &&
  494. !goog.dom.canHaveChildren(node));
  495. };
  496. /**
  497. * Construct a point at the very end of the given node.
  498. * @param {Node} node The node to create a point for.
  499. * @return {!goog.editor.range.Point} A new point.
  500. */
  501. goog.editor.range.Point.getPointAtEndOfNode = function(node) {
  502. return new goog.editor.range.Point(node, goog.editor.node.getLength(node));
  503. };
  504. /**
  505. * Saves the range by inserting carets into the HTML.
  506. *
  507. * Unlike the regular saveUsingCarets, this SavedRange normalizes text nodes.
  508. * Browsers have other bugs where they don't handle split text nodes in
  509. * contentEditable regions right.
  510. *
  511. * @param {goog.dom.AbstractRange} range The abstract range object.
  512. * @return {!goog.dom.SavedCaretRange} A saved caret range that normalizes
  513. * text nodes.
  514. */
  515. goog.editor.range.saveUsingNormalizedCarets = function(range) {
  516. return new goog.editor.range.NormalizedCaretRange_(range);
  517. };
  518. /**
  519. * Saves the range using carets, but normalizes text nodes when carets
  520. * are removed.
  521. * @see goog.editor.range.saveUsingNormalizedCarets
  522. * @param {goog.dom.AbstractRange} range The range being saved.
  523. * @constructor
  524. * @extends {goog.dom.SavedCaretRange}
  525. * @private
  526. */
  527. goog.editor.range.NormalizedCaretRange_ = function(range) {
  528. goog.dom.SavedCaretRange.call(this, range);
  529. };
  530. goog.inherits(
  531. goog.editor.range.NormalizedCaretRange_, goog.dom.SavedCaretRange);
  532. /**
  533. * Normalizes text nodes whenever carets are removed from the document.
  534. * @param {goog.dom.AbstractRange=} opt_range A range whose offsets have already
  535. * been adjusted for caret removal; it will be adjusted and returned if it
  536. * is also affected by post-removal operations, such as text node
  537. * normalization.
  538. * @return {goog.dom.AbstractRange|undefined} The adjusted range, if opt_range
  539. * was provided.
  540. * @override
  541. */
  542. goog.editor.range.NormalizedCaretRange_.prototype.removeCarets = function(
  543. opt_range) {
  544. var startCaret = this.getCaret(true);
  545. var endCaret = this.getCaret(false);
  546. var node = startCaret && endCaret ?
  547. goog.dom.findCommonAncestor(startCaret, endCaret) :
  548. startCaret || endCaret;
  549. goog.editor.range.NormalizedCaretRange_.superClass_.removeCarets.call(this);
  550. if (opt_range) {
  551. return goog.editor.range.rangePreservingNormalize(node, opt_range);
  552. } else if (node) {
  553. goog.editor.range.selectionPreservingNormalize(node);
  554. }
  555. };