Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/dom/controlrange.js
2868 views
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
/**
16
* @fileoverview Utilities for working with IE control ranges.
17
*
18
* @author [email protected] (Robby Walker)
19
*/
20
21
22
goog.provide('goog.dom.ControlRange');
23
goog.provide('goog.dom.ControlRangeIterator');
24
25
goog.require('goog.array');
26
goog.require('goog.dom');
27
goog.require('goog.dom.AbstractMultiRange');
28
goog.require('goog.dom.AbstractRange');
29
goog.require('goog.dom.RangeIterator');
30
goog.require('goog.dom.RangeType');
31
goog.require('goog.dom.SavedRange');
32
goog.require('goog.dom.TagWalkType');
33
goog.require('goog.dom.TextRange');
34
goog.require('goog.iter.StopIteration');
35
goog.require('goog.userAgent');
36
37
38
39
/**
40
* Create a new control selection with no properties. Do not use this
41
* constructor: use one of the goog.dom.Range.createFrom* methods instead.
42
* @constructor
43
* @extends {goog.dom.AbstractMultiRange}
44
* @final
45
*/
46
goog.dom.ControlRange = function() {
47
/**
48
* The IE control range obejct.
49
* @private {Object}
50
*/
51
this.range_ = null;
52
53
/**
54
* Cached list of elements.
55
* @private {Array<Element>}
56
*/
57
this.elements_ = null;
58
59
/**
60
* Cached sorted list of elements.
61
* @private {Array<Element>}
62
*/
63
this.sortedElements_ = null;
64
};
65
goog.inherits(goog.dom.ControlRange, goog.dom.AbstractMultiRange);
66
67
68
/**
69
* Create a new range wrapper from the given browser range object. Do not use
70
* this method directly - please use goog.dom.Range.createFrom* instead.
71
* @param {Object} controlRange The browser range object.
72
* @return {!goog.dom.ControlRange} A range wrapper object.
73
*/
74
goog.dom.ControlRange.createFromBrowserRange = function(controlRange) {
75
var range = new goog.dom.ControlRange();
76
range.range_ = controlRange;
77
return range;
78
};
79
80
81
/**
82
* Create a new range wrapper that selects the given element. Do not use
83
* this method directly - please use goog.dom.Range.createFrom* instead.
84
* @param {...Element} var_args The element(s) to select.
85
* @return {!goog.dom.ControlRange} A range wrapper object.
86
*/
87
goog.dom.ControlRange.createFromElements = function(var_args) {
88
var range = goog.dom.getOwnerDocument(arguments[0]).body.createControlRange();
89
for (var i = 0, len = arguments.length; i < len; i++) {
90
range.addElement(arguments[i]);
91
}
92
return goog.dom.ControlRange.createFromBrowserRange(range);
93
};
94
95
96
// Method implementations
97
98
99
/**
100
* Clear cached values.
101
* @private
102
*/
103
goog.dom.ControlRange.prototype.clearCachedValues_ = function() {
104
this.elements_ = null;
105
this.sortedElements_ = null;
106
};
107
108
109
/** @override */
110
goog.dom.ControlRange.prototype.clone = function() {
111
return goog.dom.ControlRange.createFromElements.apply(
112
this, this.getElements());
113
};
114
115
116
/** @override */
117
goog.dom.ControlRange.prototype.getType = function() {
118
return goog.dom.RangeType.CONTROL;
119
};
120
121
122
/** @override */
123
goog.dom.ControlRange.prototype.getBrowserRangeObject = function() {
124
return this.range_ || document.body.createControlRange();
125
};
126
127
128
/** @override */
129
goog.dom.ControlRange.prototype.setBrowserRangeObject = function(nativeRange) {
130
if (!goog.dom.AbstractRange.isNativeControlRange(nativeRange)) {
131
return false;
132
}
133
this.range_ = nativeRange;
134
return true;
135
};
136
137
138
/** @override */
139
goog.dom.ControlRange.prototype.getTextRangeCount = function() {
140
return this.range_ ? this.range_.length : 0;
141
};
142
143
144
/** @override */
145
goog.dom.ControlRange.prototype.getTextRange = function(i) {
146
return goog.dom.TextRange.createFromNodeContents(this.range_.item(i));
147
};
148
149
150
/** @override */
151
goog.dom.ControlRange.prototype.getContainer = function() {
152
return goog.dom.findCommonAncestor.apply(null, this.getElements());
153
};
154
155
156
/** @override */
157
goog.dom.ControlRange.prototype.getStartNode = function() {
158
return this.getSortedElements()[0];
159
};
160
161
162
/** @override */
163
goog.dom.ControlRange.prototype.getStartOffset = function() {
164
return 0;
165
};
166
167
168
/** @override */
169
goog.dom.ControlRange.prototype.getEndNode = function() {
170
var sorted = this.getSortedElements();
171
var startsLast = /** @type {Node} */ (goog.array.peek(sorted));
172
return /** @type {Node} */ (goog.array.find(sorted, function(el) {
173
return goog.dom.contains(el, startsLast);
174
}));
175
};
176
177
178
/** @override */
179
goog.dom.ControlRange.prototype.getEndOffset = function() {
180
return this.getEndNode().childNodes.length;
181
};
182
183
184
// TODO(robbyw): Figure out how to unify getElements with TextRange API.
185
/**
186
* @return {!Array<Element>} Array of elements in the control range.
187
*/
188
goog.dom.ControlRange.prototype.getElements = function() {
189
if (!this.elements_) {
190
this.elements_ = [];
191
if (this.range_) {
192
for (var i = 0; i < this.range_.length; i++) {
193
this.elements_.push(this.range_.item(i));
194
}
195
}
196
}
197
198
return this.elements_;
199
};
200
201
202
/**
203
* @return {!Array<Element>} Array of elements comprising the control range,
204
* sorted by document order.
205
*/
206
goog.dom.ControlRange.prototype.getSortedElements = function() {
207
if (!this.sortedElements_) {
208
this.sortedElements_ = this.getElements().concat();
209
this.sortedElements_.sort(function(a, b) {
210
return a.sourceIndex - b.sourceIndex;
211
});
212
}
213
214
return this.sortedElements_;
215
};
216
217
218
/** @override */
219
goog.dom.ControlRange.prototype.isRangeInDocument = function() {
220
var returnValue = false;
221
222
try {
223
returnValue = goog.array.every(this.getElements(), function(element) {
224
// On IE, this throws an exception when the range is detached.
225
return goog.userAgent.IE ?
226
!!element.parentNode :
227
goog.dom.contains(element.ownerDocument.body, element);
228
});
229
} catch (e) {
230
// IE sometimes throws Invalid Argument errors for detached elements.
231
// Note: trying to return a value from the above try block can cause IE
232
// to crash. It is necessary to use the local returnValue.
233
}
234
235
return returnValue;
236
};
237
238
239
/** @override */
240
goog.dom.ControlRange.prototype.isCollapsed = function() {
241
return !this.range_ || !this.range_.length;
242
};
243
244
245
/** @override */
246
goog.dom.ControlRange.prototype.getText = function() {
247
// TODO(robbyw): What about for table selections? Should those have text?
248
return '';
249
};
250
251
252
/** @override */
253
goog.dom.ControlRange.prototype.getHtmlFragment = function() {
254
return goog.array.map(this.getSortedElements(), goog.dom.getOuterHtml)
255
.join('');
256
};
257
258
259
/** @override */
260
goog.dom.ControlRange.prototype.getValidHtml = function() {
261
return this.getHtmlFragment();
262
};
263
264
265
/** @override */
266
goog.dom.ControlRange.prototype.getPastableHtml =
267
goog.dom.ControlRange.prototype.getValidHtml;
268
269
270
/** @override */
271
goog.dom.ControlRange.prototype.__iterator__ = function(opt_keys) {
272
return new goog.dom.ControlRangeIterator(this);
273
};
274
275
276
// RANGE ACTIONS
277
278
279
/** @override */
280
goog.dom.ControlRange.prototype.select = function() {
281
if (this.range_) {
282
this.range_.select();
283
}
284
};
285
286
287
/** @override */
288
goog.dom.ControlRange.prototype.removeContents = function() {
289
// TODO(robbyw): Test implementing with execCommand('Delete')
290
if (this.range_) {
291
var nodes = [];
292
for (var i = 0, len = this.range_.length; i < len; i++) {
293
nodes.push(this.range_.item(i));
294
}
295
goog.array.forEach(nodes, goog.dom.removeNode);
296
297
this.collapse(false);
298
}
299
};
300
301
302
/** @override */
303
goog.dom.ControlRange.prototype.replaceContentsWithNode = function(node) {
304
// Control selections have to have the node inserted before removing the
305
// selection contents because a collapsed control range doesn't have start or
306
// end nodes.
307
var result = this.insertNode(node, true);
308
309
if (!this.isCollapsed()) {
310
this.removeContents();
311
}
312
313
return result;
314
};
315
316
317
// SAVE/RESTORE
318
319
320
/** @override */
321
goog.dom.ControlRange.prototype.saveUsingDom = function() {
322
return new goog.dom.DomSavedControlRange_(this);
323
};
324
325
326
// RANGE MODIFICATION
327
328
329
/** @override */
330
goog.dom.ControlRange.prototype.collapse = function(toAnchor) {
331
// TODO(robbyw): Should this return a text range? If so, API needs to change.
332
this.range_ = null;
333
this.clearCachedValues_();
334
};
335
336
337
// SAVED RANGE OBJECTS
338
339
340
341
/**
342
* A SavedRange implementation using DOM endpoints.
343
* @param {goog.dom.ControlRange} range The range to save.
344
* @constructor
345
* @extends {goog.dom.SavedRange}
346
* @private
347
*/
348
goog.dom.DomSavedControlRange_ = function(range) {
349
/**
350
* The element list.
351
* @type {Array<Element>}
352
* @private
353
*/
354
this.elements_ = range.getElements();
355
};
356
goog.inherits(goog.dom.DomSavedControlRange_, goog.dom.SavedRange);
357
358
359
/** @override */
360
goog.dom.DomSavedControlRange_.prototype.restoreInternal = function() {
361
var doc = this.elements_.length ?
362
goog.dom.getOwnerDocument(this.elements_[0]) :
363
document;
364
var controlRange = doc.body.createControlRange();
365
for (var i = 0, len = this.elements_.length; i < len; i++) {
366
controlRange.addElement(this.elements_[i]);
367
}
368
return goog.dom.ControlRange.createFromBrowserRange(controlRange);
369
};
370
371
372
/** @override */
373
goog.dom.DomSavedControlRange_.prototype.disposeInternal = function() {
374
goog.dom.DomSavedControlRange_.superClass_.disposeInternal.call(this);
375
delete this.elements_;
376
};
377
378
379
// RANGE ITERATION
380
381
382
383
/**
384
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
385
* adds functions to determine the portion of each text node that is selected.
386
*
387
* @param {goog.dom.ControlRange?} range The range to traverse.
388
* @constructor
389
* @extends {goog.dom.RangeIterator}
390
* @final
391
*/
392
goog.dom.ControlRangeIterator = function(range) {
393
/**
394
* The first node in the selection.
395
* @private {Node}
396
*/
397
this.startNode_ = null;
398
399
/**
400
* The last node in the selection.
401
* @private {Node}
402
*/
403
this.endNode_ = null;
404
405
/**
406
* The list of elements left to traverse.
407
* @private {Array<Element>?}
408
*/
409
this.elements_ = null;
410
411
if (range) {
412
this.elements_ = range.getSortedElements();
413
this.startNode_ = this.elements_.shift();
414
this.endNode_ = /** @type {Node} */ (goog.array.peek(this.elements_)) ||
415
this.startNode_;
416
}
417
418
goog.dom.ControlRangeIterator.base(
419
this, 'constructor', this.startNode_, false);
420
};
421
goog.inherits(goog.dom.ControlRangeIterator, goog.dom.RangeIterator);
422
423
424
/** @override */
425
goog.dom.ControlRangeIterator.prototype.getStartTextOffset = function() {
426
return 0;
427
};
428
429
430
/** @override */
431
goog.dom.ControlRangeIterator.prototype.getEndTextOffset = function() {
432
return 0;
433
};
434
435
436
/** @override */
437
goog.dom.ControlRangeIterator.prototype.getStartNode = function() {
438
return this.startNode_;
439
};
440
441
442
/** @override */
443
goog.dom.ControlRangeIterator.prototype.getEndNode = function() {
444
return this.endNode_;
445
};
446
447
448
/** @override */
449
goog.dom.ControlRangeIterator.prototype.isLast = function() {
450
return !this.depth && !this.elements_.length;
451
};
452
453
454
/**
455
* Move to the next position in the selection.
456
* Throws {@code goog.iter.StopIteration} when it passes the end of the range.
457
* @return {Node} The node at the next position.
458
* @override
459
*/
460
goog.dom.ControlRangeIterator.prototype.next = function() {
461
// Iterate over each element in the range, and all of its children.
462
if (this.isLast()) {
463
throw goog.iter.StopIteration;
464
} else if (!this.depth) {
465
var el = this.elements_.shift();
466
this.setPosition(
467
el, goog.dom.TagWalkType.START_TAG, goog.dom.TagWalkType.START_TAG);
468
return el;
469
}
470
471
// Call the super function.
472
return goog.dom.ControlRangeIterator.superClass_.next.call(this);
473
};
474
475
476
/** @override */
477
goog.dom.ControlRangeIterator.prototype.copyFrom = function(other) {
478
this.elements_ = other.elements_;
479
this.startNode_ = other.startNode_;
480
this.endNode_ = other.endNode_;
481
482
goog.dom.ControlRangeIterator.superClass_.copyFrom.call(this, other);
483
};
484
485
486
/**
487
* @return {!goog.dom.ControlRangeIterator} An identical iterator.
488
* @override
489
*/
490
goog.dom.ControlRangeIterator.prototype.clone = function() {
491
var copy = new goog.dom.ControlRangeIterator(null);
492
copy.copyFrom(this);
493
return copy;
494
};
495
496