Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/dom/browserrange/w3crange.js
1865 views
1
// Copyright 2007 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
/**
16
* @fileoverview Definition of the W3C spec following range wrapper.
17
*
18
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
19
*
20
* @author [email protected] (Robby Walker)
21
*/
22
23
24
goog.provide('goog.dom.browserrange.W3cRange');
25
26
goog.require('goog.array');
27
goog.require('goog.dom');
28
goog.require('goog.dom.NodeType');
29
goog.require('goog.dom.RangeEndpoint');
30
goog.require('goog.dom.TagName');
31
goog.require('goog.dom.browserrange.AbstractRange');
32
goog.require('goog.string');
33
goog.require('goog.userAgent');
34
35
36
37
/**
38
* The constructor for W3C specific browser ranges.
39
* @param {Range} range The range object.
40
* @constructor
41
* @extends {goog.dom.browserrange.AbstractRange}
42
*/
43
goog.dom.browserrange.W3cRange = function(range) {
44
this.range_ = range;
45
};
46
goog.inherits(
47
goog.dom.browserrange.W3cRange, goog.dom.browserrange.AbstractRange);
48
49
50
/**
51
* Returns a browser range spanning the given node's contents.
52
* @param {Node} node The node to select.
53
* @return {!Range} A browser range spanning the node's contents.
54
* @protected
55
*/
56
goog.dom.browserrange.W3cRange.getBrowserRangeForNode = function(node) {
57
var nodeRange = goog.dom.getOwnerDocument(node).createRange();
58
59
if (node.nodeType == goog.dom.NodeType.TEXT) {
60
nodeRange.setStart(node, 0);
61
nodeRange.setEnd(node, node.length);
62
} else {
63
/** @suppress {missingRequire} */
64
if (!goog.dom.browserrange.canContainRangeEndpoint(node)) {
65
var rangeParent = node.parentNode;
66
var rangeStartOffset = goog.array.indexOf(rangeParent.childNodes, node);
67
nodeRange.setStart(rangeParent, rangeStartOffset);
68
nodeRange.setEnd(rangeParent, rangeStartOffset + 1);
69
} else {
70
var tempNode, leaf = node;
71
while ((tempNode = leaf.firstChild) &&
72
/** @suppress {missingRequire} */
73
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
74
leaf = tempNode;
75
}
76
nodeRange.setStart(leaf, 0);
77
78
leaf = node;
79
/** @suppress {missingRequire} Circular dep with browserrange */
80
while ((tempNode = leaf.lastChild) &&
81
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
82
leaf = tempNode;
83
}
84
nodeRange.setEnd(
85
leaf, leaf.nodeType == goog.dom.NodeType.ELEMENT ?
86
leaf.childNodes.length :
87
leaf.length);
88
}
89
}
90
91
return nodeRange;
92
};
93
94
95
/**
96
* Returns a browser range spanning the given nodes.
97
* @param {Node} startNode The node to start with - should not be a BR.
98
* @param {number} startOffset The offset within the start node.
99
* @param {Node} endNode The node to end with - should not be a BR.
100
* @param {number} endOffset The offset within the end node.
101
* @return {!Range} A browser range spanning the node's contents.
102
* @protected
103
*/
104
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes = function(
105
startNode, startOffset, endNode, endOffset) {
106
// Create and return the range.
107
var nodeRange = goog.dom.getOwnerDocument(startNode).createRange();
108
nodeRange.setStart(startNode, startOffset);
109
nodeRange.setEnd(endNode, endOffset);
110
return nodeRange;
111
};
112
113
114
/**
115
* Creates a range object that selects the given node's text.
116
* @param {Node} node The node to select.
117
* @return {!goog.dom.browserrange.W3cRange} A Gecko range wrapper object.
118
*/
119
goog.dom.browserrange.W3cRange.createFromNodeContents = function(node) {
120
return new goog.dom.browserrange.W3cRange(
121
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
122
};
123
124
125
/**
126
* Creates a range object that selects between the given nodes.
127
* @param {Node} startNode The node to start with.
128
* @param {number} startOffset The offset within the start node.
129
* @param {Node} endNode The node to end with.
130
* @param {number} endOffset The offset within the end node.
131
* @return {!goog.dom.browserrange.W3cRange} A wrapper object.
132
*/
133
goog.dom.browserrange.W3cRange.createFromNodes = function(
134
startNode, startOffset, endNode, endOffset) {
135
return new goog.dom.browserrange.W3cRange(
136
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(
137
startNode, startOffset, endNode, endOffset));
138
};
139
140
141
/**
142
* @return {!goog.dom.browserrange.W3cRange} A clone of this range.
143
* @override
144
*/
145
goog.dom.browserrange.W3cRange.prototype.clone = function() {
146
return new this.constructor(this.range_.cloneRange());
147
};
148
149
150
/** @override */
151
goog.dom.browserrange.W3cRange.prototype.getBrowserRange = function() {
152
return this.range_;
153
};
154
155
156
/** @override */
157
goog.dom.browserrange.W3cRange.prototype.getContainer = function() {
158
return this.range_.commonAncestorContainer;
159
};
160
161
162
/** @override */
163
goog.dom.browserrange.W3cRange.prototype.getStartNode = function() {
164
return this.range_.startContainer;
165
};
166
167
168
/** @override */
169
goog.dom.browserrange.W3cRange.prototype.getStartOffset = function() {
170
return this.range_.startOffset;
171
};
172
173
174
/** @override */
175
goog.dom.browserrange.W3cRange.prototype.getEndNode = function() {
176
return this.range_.endContainer;
177
};
178
179
180
/** @override */
181
goog.dom.browserrange.W3cRange.prototype.getEndOffset = function() {
182
return this.range_.endOffset;
183
};
184
185
186
/** @override */
187
goog.dom.browserrange.W3cRange.prototype.compareBrowserRangeEndpoints =
188
function(range, thisEndpoint, otherEndpoint) {
189
return this.range_.compareBoundaryPoints(
190
otherEndpoint == goog.dom.RangeEndpoint.START ?
191
(thisEndpoint == goog.dom.RangeEndpoint.START ?
192
goog.global['Range'].START_TO_START :
193
goog.global['Range'].START_TO_END) :
194
(thisEndpoint == goog.dom.RangeEndpoint.START ?
195
goog.global['Range'].END_TO_START :
196
goog.global['Range'].END_TO_END),
197
/** @type {Range} */ (range));
198
};
199
200
201
/** @override */
202
goog.dom.browserrange.W3cRange.prototype.isCollapsed = function() {
203
return this.range_.collapsed;
204
};
205
206
207
/** @override */
208
goog.dom.browserrange.W3cRange.prototype.getText = function() {
209
return this.range_.toString();
210
};
211
212
213
/** @override */
214
goog.dom.browserrange.W3cRange.prototype.getValidHtml = function() {
215
var div = goog.dom.getDomHelper(this.range_.startContainer)
216
.createDom(goog.dom.TagName.DIV);
217
div.appendChild(this.range_.cloneContents());
218
var result = div.innerHTML;
219
220
if (goog.string.startsWith(result, '<') ||
221
!this.isCollapsed() && !goog.string.contains(result, '<')) {
222
// We attempt to mimic IE, which returns no containing element when a
223
// only text nodes are selected, does return the containing element when
224
// the selection is empty, and does return the element when multiple nodes
225
// are selected.
226
return result;
227
}
228
229
var container = this.getContainer();
230
container = container.nodeType == goog.dom.NodeType.ELEMENT ?
231
container :
232
container.parentNode;
233
234
var html = goog.dom.getOuterHtml(
235
/** @type {!Element} */ (container.cloneNode(false)));
236
return html.replace('>', '>' + result);
237
};
238
239
240
// SELECTION MODIFICATION
241
242
243
/** @override */
244
goog.dom.browserrange.W3cRange.prototype.select = function(reverse) {
245
var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
246
this.selectInternal(win.getSelection(), reverse);
247
};
248
249
250
/**
251
* Select this range.
252
* @param {Selection} selection Browser selection object.
253
* @param {*} reverse Whether to select this range in reverse.
254
* @protected
255
*/
256
goog.dom.browserrange.W3cRange.prototype.selectInternal = function(
257
selection, reverse) {
258
// Browser-specific tricks are needed to create reversed selections
259
// programatically. For this generic W3C codepath, ignore the reverse
260
// parameter.
261
selection.removeAllRanges();
262
selection.addRange(this.range_);
263
};
264
265
266
/** @override */
267
goog.dom.browserrange.W3cRange.prototype.removeContents = function() {
268
var range = this.range_;
269
range.extractContents();
270
271
if (range.startContainer.hasChildNodes()) {
272
// Remove any now empty nodes surrounding the extracted contents.
273
var rangeStartContainer =
274
range.startContainer.childNodes[range.startOffset];
275
if (rangeStartContainer) {
276
var rangePrevious = rangeStartContainer.previousSibling;
277
278
if (goog.dom.getRawTextContent(rangeStartContainer) == '') {
279
goog.dom.removeNode(rangeStartContainer);
280
}
281
282
if (rangePrevious && goog.dom.getRawTextContent(rangePrevious) == '') {
283
goog.dom.removeNode(rangePrevious);
284
}
285
}
286
}
287
288
if (goog.userAgent.EDGE_OR_IE) {
289
// Unfortunately, when deleting a portion of a single text node, IE creates
290
// an extra text node instead of modifying the nodeValue of the start node.
291
// We normalize for that behavior here, similar to code in
292
// goog.dom.browserrange.IeRange#removeContents
293
// See https://connect.microsoft.com/IE/feedback/details/746591
294
var startNode = this.getStartNode();
295
var startOffset = this.getStartOffset();
296
var endNode = this.getEndNode();
297
var endOffset = this.getEndOffset();
298
var sibling = startNode.nextSibling;
299
if (startNode == endNode && startNode.parentNode &&
300
startNode.nodeType == goog.dom.NodeType.TEXT && sibling &&
301
sibling.nodeType == goog.dom.NodeType.TEXT) {
302
startNode.nodeValue += sibling.nodeValue;
303
goog.dom.removeNode(sibling);
304
305
// Modifying the node value clears the range offsets. Reselect the
306
// position in the modified start node.
307
range.setStart(startNode, startOffset);
308
range.setEnd(endNode, endOffset);
309
}
310
}
311
};
312
313
314
/** @override */
315
goog.dom.browserrange.W3cRange.prototype.surroundContents = function(element) {
316
this.range_.surroundContents(element);
317
return element;
318
};
319
320
321
/** @override */
322
goog.dom.browserrange.W3cRange.prototype.insertNode = function(node, before) {
323
var range = this.range_.cloneRange();
324
range.collapse(before);
325
range.insertNode(node);
326
range.detach();
327
328
return node;
329
};
330
331
332
/** @override */
333
goog.dom.browserrange.W3cRange.prototype.surroundWithNodes = function(
334
startNode, endNode) {
335
var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
336
/** @suppress {missingRequire} */
337
var selectionRange = goog.dom.Range.createFromWindow(win);
338
if (selectionRange) {
339
var sNode = selectionRange.getStartNode();
340
var eNode = selectionRange.getEndNode();
341
var sOffset = selectionRange.getStartOffset();
342
var eOffset = selectionRange.getEndOffset();
343
}
344
345
var clone1 = this.range_.cloneRange();
346
var clone2 = this.range_.cloneRange();
347
348
clone1.collapse(false);
349
clone2.collapse(true);
350
351
clone1.insertNode(endNode);
352
clone2.insertNode(startNode);
353
354
clone1.detach();
355
clone2.detach();
356
357
if (selectionRange) {
358
// There are 4 ways that surroundWithNodes can wreck the saved
359
// selection object. All of them happen when an inserted node splits
360
// a text node, and one of the end points of the selection was in the
361
// latter half of that text node.
362
//
363
// Clients of this library should use saveUsingCarets to avoid this
364
// problem. Unfortunately, saveUsingCarets uses this method, so that's
365
// not really an option for us. :( We just recompute the offsets.
366
var isInsertedNode = function(n) { return n == startNode || n == endNode; };
367
if (sNode.nodeType == goog.dom.NodeType.TEXT) {
368
while (sOffset > sNode.length) {
369
sOffset -= sNode.length;
370
do {
371
sNode = sNode.nextSibling;
372
} while (isInsertedNode(sNode));
373
}
374
}
375
376
if (eNode.nodeType == goog.dom.NodeType.TEXT) {
377
while (eOffset > eNode.length) {
378
eOffset -= eNode.length;
379
do {
380
eNode = eNode.nextSibling;
381
} while (isInsertedNode(eNode));
382
}
383
}
384
385
/** @suppress {missingRequire} */
386
goog.dom.Range
387
.createFromNodes(
388
sNode, /** @type {number} */ (sOffset), eNode,
389
/** @type {number} */ (eOffset))
390
.select();
391
}
392
};
393
394
395
/** @override */
396
goog.dom.browserrange.W3cRange.prototype.collapse = function(toStart) {
397
this.range_.collapse(toStart);
398
};
399
400