rangy-rails 1.3alpha.772.0 → 1.3alpha.780.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -28,12 +28,15 @@ Easy! Just edit `app/assets/javascripts/application.js` and adjust it to your he
28
28
 
29
29
  Here's an example configuration that would include Rangy and all of it's modules:
30
30
 
31
- //= require rangy-core.js
32
- //= require rangy-cssclassapplier.js
33
- //= require rangy-highlighter.js
34
- //= require rangy-selectionsaverestore.js
35
- //= require rangy-serializer.js
36
- //= require rangy-textrange.js
31
+ //= require rangy-core
32
+ //= require rangy-cssclassapplier
33
+ //= require rangy-highlighter
34
+ //= require rangy-position
35
+ //= require rangy-selectionsaverestore
36
+ //= require rangy-serializer
37
+ //= require rangy-textrange
38
+
39
+ Normally you would only include the modules you're actually using in your project.
37
40
 
38
41
  ## Contributing
39
42
 
@@ -1,5 +1,5 @@
1
1
  module Rangy
2
2
  module Rails
3
- VERSION = "1.3alpha.772.0"
3
+ VERSION = "1.3alpha.780.0"
4
4
  end
5
5
  end
@@ -4,3641 +4,7 @@
4
4
  *
5
5
  * Copyright 2013, Tim Down
6
6
  * Licensed under the MIT license.
7
- * Version: 1.3alpha.772
8
- * Build date: 26 February 2013
7
+ * Version: 1.3alpha.780M
8
+ * Build date: 17 May 2013
9
9
  */
10
-
11
- var rangy;
12
- rangy = rangy || (function() {
13
-
14
- var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
15
-
16
- // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
17
- // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
18
- var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
19
- "commonAncestorContainer"];
20
-
21
- // Minimal set of methods required for DOM Level 2 Range compliance
22
- var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
23
- "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
24
- "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
25
-
26
- var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
27
-
28
- // Subset of TextRange's full set of methods that we're interested in
29
- var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
30
- "setEndPoint", "getBoundingClientRect"];
31
-
32
- /*----------------------------------------------------------------------------------------------------------------*/
33
-
34
- // Trio of functions taken from Peter Michaux's article:
35
- // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
36
- function isHostMethod(o, p) {
37
- var t = typeof o[p];
38
- return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
39
- }
40
-
41
- function isHostObject(o, p) {
42
- return !!(typeof o[p] == OBJECT && o[p]);
43
- }
44
-
45
- function isHostProperty(o, p) {
46
- return typeof o[p] != UNDEFINED;
47
- }
48
-
49
- // Creates a convenience function to save verbose repeated calls to tests functions
50
- function createMultiplePropertyTest(testFunc) {
51
- return function(o, props) {
52
- var i = props.length;
53
- while (i--) {
54
- if (!testFunc(o, props[i])) {
55
- return false;
56
- }
57
- }
58
- return true;
59
- };
60
- }
61
-
62
- // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
63
- var areHostMethods = createMultiplePropertyTest(isHostMethod);
64
- var areHostObjects = createMultiplePropertyTest(isHostObject);
65
- var areHostProperties = createMultiplePropertyTest(isHostProperty);
66
-
67
- function isTextRange(range) {
68
- return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
69
- }
70
-
71
- function getBody(doc) {
72
- return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
73
- }
74
-
75
- var modules = {};
76
-
77
- var api = {
78
- version: "1.3alpha.772",
79
- initialized: false,
80
- supported: true,
81
-
82
- util: {
83
- isHostMethod: isHostMethod,
84
- isHostObject: isHostObject,
85
- isHostProperty: isHostProperty,
86
- areHostMethods: areHostMethods,
87
- areHostObjects: areHostObjects,
88
- areHostProperties: areHostProperties,
89
- isTextRange: isTextRange,
90
- getBody: getBody
91
- },
92
-
93
- features: {},
94
-
95
- modules: modules,
96
- config: {
97
- alertOnFail: true,
98
- alertOnWarn: false,
99
- preferTextRange: false
100
- }
101
- };
102
-
103
- function consoleLog(msg) {
104
- if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
105
- window.console.log(msg);
106
- }
107
- }
108
-
109
- function alertOrLog(msg, shouldAlert) {
110
- if (shouldAlert) {
111
- window.alert(msg);
112
- } else {
113
- consoleLog(msg);
114
- }
115
- }
116
-
117
- function fail(reason) {
118
- api.initialized = true;
119
- api.supported = false;
120
- alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail);
121
- }
122
-
123
- api.fail = fail;
124
-
125
- function warn(msg) {
126
- alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
127
- }
128
-
129
- api.warn = warn;
130
-
131
- // Add utility extend() method
132
- if ({}.hasOwnProperty) {
133
- api.util.extend = function(obj, props, deep) {
134
- var o, p;
135
- for (var i in props) {
136
- if (props.hasOwnProperty(i)) {
137
- o = obj[i];
138
- p = props[i];
139
- //if (deep) alert([o !== null, typeof o == "object", p !== null, typeof p == "object"])
140
- if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
141
- api.util.extend(o, p, true);
142
- }
143
- obj[i] = p;
144
- }
145
- }
146
- return obj;
147
- };
148
- } else {
149
- fail("hasOwnProperty not supported");
150
- }
151
-
152
- // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
153
- (function() {
154
- var el = document.createElement("div");
155
- el.appendChild(document.createElement("span"));
156
- var slice = [].slice;
157
- var toArray;
158
- try {
159
- if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
160
- toArray = function(arrayLike) {
161
- return slice.call(arrayLike, 0);
162
- };
163
- }
164
- } catch (e) {}
165
-
166
- if (!toArray) {
167
- toArray = function(arrayLike) {
168
- var arr = [];
169
- for (var i = 0, len = arrayLike.length; i < len; ++i) {
170
- arr[i] = arrayLike[i];
171
- }
172
- return arr;
173
- };
174
- }
175
-
176
- api.util.toArray = toArray;
177
- })();
178
-
179
-
180
- // Very simple event handler wrapper function that doesn't attempt to solve issue such as "this" handling or
181
- // normalization of event properties
182
- var addListener;
183
- if (isHostMethod(document, "addEventListener")) {
184
- addListener = function(obj, eventType, listener) {
185
- obj.addEventListener(eventType, listener, false);
186
- };
187
- } else if (isHostMethod(document, "attachEvent")) {
188
- addListener = function(obj, eventType, listener) {
189
- obj.attachEvent("on" + eventType, listener);
190
- };
191
- } else {
192
- fail("Document does not have required addEventListener or attachEvent method");
193
- }
194
-
195
- api.util.addListener = addListener;
196
-
197
- var initListeners = [];
198
-
199
- function getErrorDesc(ex) {
200
- return ex.message || ex.description || String(ex);
201
- }
202
-
203
- // Initialization
204
- function init() {
205
- if (api.initialized) {
206
- return;
207
- }
208
- var testRange;
209
- var implementsDomRange = false, implementsTextRange = false;
210
-
211
- // First, perform basic feature tests
212
-
213
- if (isHostMethod(document, "createRange")) {
214
- testRange = document.createRange();
215
- if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
216
- implementsDomRange = true;
217
- }
218
- testRange.detach();
219
- }
220
-
221
- var body = getBody(document);
222
- if (!body || body.nodeName.toLowerCase() != "body") {
223
- fail("No body element found");
224
- return;
225
- }
226
-
227
- if (body && isHostMethod(body, "createTextRange")) {
228
- testRange = body.createTextRange();
229
- if (isTextRange(testRange)) {
230
- implementsTextRange = true;
231
- }
232
- }
233
-
234
- if (!implementsDomRange && !implementsTextRange) {
235
- fail("Neither Range nor TextRange are available");
236
- return;
237
- }
238
-
239
- api.initialized = true;
240
- api.features = {
241
- implementsDomRange: implementsDomRange,
242
- implementsTextRange: implementsTextRange
243
- };
244
-
245
- // Initialize modules
246
- var module, errorMessage;
247
- for (var moduleName in modules) {
248
- if ( (module = modules[moduleName]) instanceof Module ) {
249
- module.init();
250
- }
251
- }
252
-
253
- // Call init listeners
254
- for (var i = 0, len = initListeners.length; i < len; ++i) {
255
- try {
256
- initListeners[i](api);
257
- } catch (ex) {
258
- errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
259
- consoleLog(errorMessage);
260
- }
261
- }
262
- }
263
-
264
- // Allow external scripts to initialize this library in case it's loaded after the document has loaded
265
- api.init = init;
266
-
267
- // Execute listener immediately if already initialized
268
- api.addInitListener = function(listener) {
269
- if (api.initialized) {
270
- listener(api);
271
- } else {
272
- initListeners.push(listener);
273
- }
274
- };
275
-
276
- var createMissingNativeApiListeners = [];
277
-
278
- api.addCreateMissingNativeApiListener = function(listener) {
279
- createMissingNativeApiListeners.push(listener);
280
- };
281
-
282
- function createMissingNativeApi(win) {
283
- win = win || window;
284
- init();
285
-
286
- // Notify listeners
287
- for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
288
- createMissingNativeApiListeners[i](win);
289
- }
290
- }
291
-
292
- api.createMissingNativeApi = createMissingNativeApi;
293
-
294
- function Module(name, initializer) {
295
- this.name = name;
296
- this.initialized = false;
297
- this.supported = false;
298
- this.init = initializer;
299
- }
300
-
301
- Module.prototype = {
302
- fail: function(reason) {
303
- this.initialized = true;
304
- this.supported = false;
305
- throw new Error("Module '" + this.name + "' failed to load: " + reason);
306
- },
307
-
308
- warn: function(msg) {
309
- api.warn("Module " + this.name + ": " + msg);
310
- },
311
-
312
- deprecationNotice: function(deprecated, replacement) {
313
- api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use "
314
- + replacement + " instead");
315
- },
316
-
317
- createError: function(msg) {
318
- return new Error("Error in Rangy " + this.name + " module: " + msg);
319
- }
320
- };
321
-
322
- api.createModule = function(name, initFunc) {
323
- var module = new Module(name, function() {
324
- if (!module.initialized) {
325
- module.initialized = true;
326
- try {
327
- initFunc(api, module);
328
- module.supported = true;
329
- } catch (ex) {
330
- var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
331
- consoleLog(errorMessage);
332
- }
333
- }
334
- });
335
- modules[name] = module;
336
- };
337
-
338
- api.requireModules = function(moduleNames) {
339
- for (var i = 0, len = moduleNames.length, module, moduleName; i < len; ++i) {
340
- moduleName = moduleNames[i];
341
-
342
- module = modules[moduleName];
343
- if (!module || !(module instanceof Module)) {
344
- throw new Error("required module '" + moduleName + "' not found");
345
- }
346
-
347
- module.init();
348
-
349
- if (!module.supported) {
350
- throw new Error("required module '" + moduleName + "' not supported");
351
- }
352
- }
353
- };
354
-
355
- /*----------------------------------------------------------------------------------------------------------------*/
356
-
357
- // Wait for document to load before running tests
358
-
359
- var docReady = false;
360
-
361
- var loadHandler = function(e) {
362
- if (!docReady) {
363
- docReady = true;
364
- if (!api.initialized) {
365
- init();
366
- }
367
- }
368
- };
369
-
370
- // Test whether we have window and document objects that we will need
371
- if (typeof window == UNDEFINED) {
372
- fail("No window found");
373
- return;
374
- }
375
- if (typeof document == UNDEFINED) {
376
- fail("No document found");
377
- return;
378
- }
379
-
380
- if (isHostMethod(document, "addEventListener")) {
381
- document.addEventListener("DOMContentLoaded", loadHandler, false);
382
- }
383
-
384
- // Add a fallback in case the DOMContentLoaded event isn't supported
385
- addListener(window, "load", loadHandler);
386
-
387
- return api;
388
- })();
389
-
390
- rangy.createModule("DomUtil", function(api, module) {
391
- var UNDEF = "undefined";
392
- var util = api.util;
393
-
394
- // Perform feature tests
395
- if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
396
- module.fail("document missing a Node creation method");
397
- }
398
-
399
- if (!util.isHostMethod(document, "getElementsByTagName")) {
400
- module.fail("document missing getElementsByTagName method");
401
- }
402
-
403
- var el = document.createElement("div");
404
- if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
405
- !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
406
- module.fail("Incomplete Element implementation");
407
- }
408
-
409
- // innerHTML is required for Range's createContextualFragment method
410
- if (!util.isHostProperty(el, "innerHTML")) {
411
- module.fail("Element is missing innerHTML property");
412
- }
413
-
414
- var textNode = document.createTextNode("test");
415
- if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
416
- !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
417
- !util.areHostProperties(textNode, ["data"]))) {
418
- module.fail("Incomplete Text Node implementation");
419
- }
420
-
421
- /*----------------------------------------------------------------------------------------------------------------*/
422
-
423
- // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
424
- // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
425
- // contains just the document as a single element and the value searched for is the document.
426
- var arrayContains = /*Array.prototype.indexOf ?
427
- function(arr, val) {
428
- return arr.indexOf(val) > -1;
429
- }:*/
430
-
431
- function(arr, val) {
432
- var i = arr.length;
433
- while (i--) {
434
- if (arr[i] === val) {
435
- return true;
436
- }
437
- }
438
- return false;
439
- };
440
-
441
- // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
442
- function isHtmlNamespace(node) {
443
- var ns;
444
- return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
445
- }
446
-
447
- function parentElement(node) {
448
- var parent = node.parentNode;
449
- return (parent.nodeType == 1) ? parent : null;
450
- }
451
-
452
- function getNodeIndex(node) {
453
- var i = 0;
454
- while( (node = node.previousSibling) ) {
455
- ++i;
456
- }
457
- return i;
458
- }
459
-
460
- function getNodeLength(node) {
461
- switch (node.nodeType) {
462
- case 7:
463
- case 10:
464
- return 0;
465
- case 3:
466
- case 8:
467
- return node.length;
468
- default:
469
- return node.childNodes.length;
470
- }
471
- }
472
-
473
- function getCommonAncestor(node1, node2) {
474
- var ancestors = [], n;
475
- for (n = node1; n; n = n.parentNode) {
476
- ancestors.push(n);
477
- }
478
-
479
- for (n = node2; n; n = n.parentNode) {
480
- if (arrayContains(ancestors, n)) {
481
- return n;
482
- }
483
- }
484
-
485
- return null;
486
- }
487
-
488
- function isAncestorOf(ancestor, descendant, selfIsAncestor) {
489
- var n = selfIsAncestor ? descendant : descendant.parentNode;
490
- while (n) {
491
- if (n === ancestor) {
492
- return true;
493
- } else {
494
- n = n.parentNode;
495
- }
496
- }
497
- return false;
498
- }
499
-
500
- function isOrIsAncestorOf(ancestor, descendant) {
501
- return isAncestorOf(ancestor, descendant, true);
502
- }
503
-
504
- function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
505
- var p, n = selfIsAncestor ? node : node.parentNode;
506
- while (n) {
507
- p = n.parentNode;
508
- if (p === ancestor) {
509
- return n;
510
- }
511
- n = p;
512
- }
513
- return null;
514
- }
515
-
516
- function isCharacterDataNode(node) {
517
- var t = node.nodeType;
518
- return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
519
- }
520
-
521
- function isTextOrCommentNode(node) {
522
- if (!node) {
523
- return false;
524
- }
525
- var t = node.nodeType;
526
- return t == 3 || t == 8 ; // Text or Comment
527
- }
528
-
529
- function insertAfter(node, precedingNode) {
530
- var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
531
- if (nextNode) {
532
- parent.insertBefore(node, nextNode);
533
- } else {
534
- parent.appendChild(node);
535
- }
536
- return node;
537
- }
538
-
539
- // Note that we cannot use splitText() because it is bugridden in IE 9.
540
- function splitDataNode(node, index, positionsToPreserve) {
541
- var newNode = node.cloneNode(false);
542
- newNode.deleteData(0, index);
543
- node.deleteData(index, node.length - index);
544
- insertAfter(newNode, node);
545
-
546
- // Preserve positions
547
- if (positionsToPreserve) {
548
- for (var i = 0, position; position = positionsToPreserve[i++]; ) {
549
- // Handle case where position was inside the portion of node after the split point
550
- if (position.node == node && position.offset > index) {
551
- position.node = newNode;
552
- position.offset -= index;
553
- }
554
- // Handle the case where the position is a node offset within node's parent
555
- else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
556
- ++position.offset;
557
- }
558
- }
559
- }
560
- return newNode;
561
- }
562
-
563
- function getDocument(node) {
564
- if (node.nodeType == 9) {
565
- return node;
566
- } else if (typeof node.ownerDocument != UNDEF) {
567
- return node.ownerDocument;
568
- } else if (typeof node.document != UNDEF) {
569
- return node.document;
570
- } else if (node.parentNode) {
571
- return getDocument(node.parentNode);
572
- } else {
573
- throw module.createError("getDocument: no document found for node");
574
- }
575
- }
576
-
577
- function getWindow(node) {
578
- var doc = getDocument(node);
579
- if (typeof doc.defaultView != UNDEF) {
580
- return doc.defaultView;
581
- } else if (typeof doc.parentWindow != UNDEF) {
582
- return doc.parentWindow;
583
- } else {
584
- throw module.createError("Cannot get a window object for node");
585
- }
586
- }
587
-
588
- function getIframeDocument(iframeEl) {
589
- if (typeof iframeEl.contentDocument != UNDEF) {
590
- return iframeEl.contentDocument;
591
- } else if (typeof iframeEl.contentWindow != UNDEF) {
592
- return iframeEl.contentWindow.document;
593
- } else {
594
- throw module.createError("getIframeDocument: No Document object found for iframe element");
595
- }
596
- }
597
-
598
- function getIframeWindow(iframeEl) {
599
- if (typeof iframeEl.contentWindow != UNDEF) {
600
- return iframeEl.contentWindow;
601
- } else if (typeof iframeEl.contentDocument != UNDEF) {
602
- return iframeEl.contentDocument.defaultView;
603
- } else {
604
- throw module.createError("getIframeWindow: No Window object found for iframe element");
605
- }
606
- }
607
-
608
- // This looks bad. Is it worth it?
609
- function isWindow(obj) {
610
- return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
611
- }
612
-
613
- function getContentDocument(obj, module, methodName) {
614
- var doc;
615
-
616
- if (!obj) {
617
- doc = document;
618
- }
619
-
620
- // Test if a DOM node has been passed and obtain a document object for it if so
621
- else if (util.isHostProperty(obj, "nodeType")) {
622
- doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe")
623
- ? getIframeDocument(obj) : getDocument(obj);
624
- }
625
-
626
- // Test if the doc parameter appears to be a Window object
627
- else if (isWindow(obj)) {
628
- doc = obj.document;
629
- }
630
-
631
- if (!doc) {
632
- throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
633
- }
634
-
635
- return doc;
636
- }
637
-
638
- function getRootContainer(node) {
639
- var parent;
640
- while ( (parent = node.parentNode) ) {
641
- node = parent;
642
- }
643
- return node;
644
- }
645
-
646
- function comparePoints(nodeA, offsetA, nodeB, offsetB) {
647
- // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
648
- var nodeC, root, childA, childB, n;
649
- if (nodeA == nodeB) {
650
- // Case 1: nodes are the same
651
- return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
652
- } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
653
- // Case 2: node C (container B or an ancestor) is a child node of A
654
- return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
655
- } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
656
- // Case 3: node C (container A or an ancestor) is a child node of B
657
- return getNodeIndex(nodeC) < offsetB ? -1 : 1;
658
- } else {
659
- // Case 4: containers are siblings or descendants of siblings
660
- root = getCommonAncestor(nodeA, nodeB);
661
- childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
662
- childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
663
-
664
- if (childA === childB) {
665
- // This shouldn't be possible
666
- throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
667
- } else {
668
- n = root.firstChild;
669
- while (n) {
670
- if (n === childA) {
671
- return -1;
672
- } else if (n === childB) {
673
- return 1;
674
- }
675
- n = n.nextSibling;
676
- }
677
- }
678
- }
679
- }
680
-
681
- /*----------------------------------------------------------------------------------------------------------------*/
682
-
683
- // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
684
- var crashyTextNodes = false;
685
-
686
- function isBrokenNode(node) {
687
- try {
688
- node.parentNode;
689
- return false;
690
- } catch (e) {
691
- return true;
692
- }
693
- }
694
-
695
- (function() {
696
- var el = document.createElement("b");
697
- el.innerHTML = "1";
698
- var textNode = el.firstChild;
699
- el.innerHTML = "<br>";
700
- crashyTextNodes = isBrokenNode(textNode);
701
-
702
- api.features.crashyTextNodes = crashyTextNodes;
703
- })();
704
-
705
- /*----------------------------------------------------------------------------------------------------------------*/
706
-
707
- function inspectNode(node) {
708
- if (!node) {
709
- return "[No node]";
710
- }
711
- if (crashyTextNodes && isBrokenNode(node)) {
712
- return "[Broken node]";
713
- }
714
- if (isCharacterDataNode(node)) {
715
- return '"' + node.data + '"';
716
- }
717
- if (node.nodeType == 1) {
718
- var idAttr = node.id ? ' id="' + node.id + '"' : "";
719
- return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "][" + node.innerHTML.slice(0, 20) + "]";
720
- }
721
- return node.nodeName;
722
- }
723
-
724
- function fragmentFromNodeChildren(node) {
725
- var fragment = getDocument(node).createDocumentFragment(), child;
726
- while ( (child = node.firstChild) ) {
727
- fragment.appendChild(child);
728
- }
729
- return fragment;
730
- }
731
-
732
- var getComputedStyleProperty;
733
- if (typeof window.getComputedStyle != UNDEF) {
734
- getComputedStyleProperty = function(el, propName) {
735
- return getWindow(el).getComputedStyle(el, null)[propName];
736
- };
737
- } else if (typeof document.documentElement.currentStyle != UNDEF) {
738
- getComputedStyleProperty = function(el, propName) {
739
- return el.currentStyle[propName];
740
- };
741
- } else {
742
- module.fail("No means of obtaining computed style properties found");
743
- }
744
-
745
- function NodeIterator(root) {
746
- this.root = root;
747
- this._next = root;
748
- }
749
-
750
- NodeIterator.prototype = {
751
- _current: null,
752
-
753
- hasNext: function() {
754
- return !!this._next;
755
- },
756
-
757
- next: function() {
758
- var n = this._current = this._next;
759
- var child, next;
760
- if (this._current) {
761
- child = n.firstChild;
762
- if (child) {
763
- this._next = child;
764
- } else {
765
- next = null;
766
- while ((n !== this.root) && !(next = n.nextSibling)) {
767
- n = n.parentNode;
768
- }
769
- this._next = next;
770
- }
771
- }
772
- return this._current;
773
- },
774
-
775
- detach: function() {
776
- this._current = this._next = this.root = null;
777
- }
778
- };
779
-
780
- function createIterator(root) {
781
- return new NodeIterator(root);
782
- }
783
-
784
- function DomPosition(node, offset) {
785
- this.node = node;
786
- this.offset = offset;
787
- }
788
-
789
- DomPosition.prototype = {
790
- equals: function(pos) {
791
- return !!pos && this.node === pos.node && this.offset == pos.offset;
792
- },
793
-
794
- inspect: function() {
795
- return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
796
- },
797
-
798
- toString: function() {
799
- return this.inspect();
800
- }
801
- };
802
-
803
- function DOMException(codeName) {
804
- this.code = this[codeName];
805
- this.codeName = codeName;
806
- this.message = "DOMException: " + this.codeName;
807
- }
808
-
809
- DOMException.prototype = {
810
- INDEX_SIZE_ERR: 1,
811
- HIERARCHY_REQUEST_ERR: 3,
812
- WRONG_DOCUMENT_ERR: 4,
813
- NO_MODIFICATION_ALLOWED_ERR: 7,
814
- NOT_FOUND_ERR: 8,
815
- NOT_SUPPORTED_ERR: 9,
816
- INVALID_STATE_ERR: 11
817
- };
818
-
819
- DOMException.prototype.toString = function() {
820
- return this.message;
821
- };
822
-
823
- api.dom = {
824
- arrayContains: arrayContains,
825
- isHtmlNamespace: isHtmlNamespace,
826
- parentElement: parentElement,
827
- getNodeIndex: getNodeIndex,
828
- getNodeLength: getNodeLength,
829
- getCommonAncestor: getCommonAncestor,
830
- isAncestorOf: isAncestorOf,
831
- isOrIsAncestorOf: isOrIsAncestorOf,
832
- getClosestAncestorIn: getClosestAncestorIn,
833
- isCharacterDataNode: isCharacterDataNode,
834
- isTextOrCommentNode: isTextOrCommentNode,
835
- insertAfter: insertAfter,
836
- splitDataNode: splitDataNode,
837
- getDocument: getDocument,
838
- getWindow: getWindow,
839
- getIframeWindow: getIframeWindow,
840
- getIframeDocument: getIframeDocument,
841
- getBody: util.getBody,
842
- isWindow: isWindow,
843
- getContentDocument: getContentDocument,
844
- getRootContainer: getRootContainer,
845
- comparePoints: comparePoints,
846
- isBrokenNode: isBrokenNode,
847
- inspectNode: inspectNode,
848
- getComputedStyleProperty: getComputedStyleProperty,
849
- fragmentFromNodeChildren: fragmentFromNodeChildren,
850
- createIterator: createIterator,
851
- DomPosition: DomPosition
852
- };
853
-
854
- api.DOMException = DOMException;
855
- });
856
- rangy.createModule("DomRange", function(api, module) {
857
- api.requireModules( ["DomUtil"] );
858
-
859
- var dom = api.dom;
860
- var util = api.util;
861
- var DomPosition = dom.DomPosition;
862
- var DOMException = api.DOMException;
863
-
864
- var isCharacterDataNode = dom.isCharacterDataNode;
865
- var getNodeIndex = dom.getNodeIndex;
866
- var isOrIsAncestorOf = dom.isOrIsAncestorOf;
867
- var getDocument = dom.getDocument;
868
- var comparePoints = dom.comparePoints;
869
- var splitDataNode = dom.splitDataNode;
870
- var getClosestAncestorIn = dom.getClosestAncestorIn;
871
- var getNodeLength = dom.getNodeLength;
872
- var arrayContains = dom.arrayContains;
873
- var getRootContainer = dom.getRootContainer;
874
- var crashyTextNodes = api.features.crashyTextNodes;
875
-
876
- /*----------------------------------------------------------------------------------------------------------------*/
877
-
878
- // Utility functions
879
-
880
- function isNonTextPartiallySelected(node, range) {
881
- return (node.nodeType != 3) &&
882
- (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
883
- }
884
-
885
- function getRangeDocument(range) {
886
- return range.document || getDocument(range.startContainer);
887
- }
888
-
889
- function getBoundaryBeforeNode(node) {
890
- return new DomPosition(node.parentNode, getNodeIndex(node));
891
- }
892
-
893
- function getBoundaryAfterNode(node) {
894
- return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
895
- }
896
-
897
- function insertNodeAtPosition(node, n, o) {
898
- var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
899
- if (isCharacterDataNode(n)) {
900
- if (o == n.length) {
901
- dom.insertAfter(node, n);
902
- } else {
903
- n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
904
- }
905
- } else if (o >= n.childNodes.length) {
906
- n.appendChild(node);
907
- } else {
908
- n.insertBefore(node, n.childNodes[o]);
909
- }
910
- return firstNodeInserted;
911
- }
912
-
913
- function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
914
- assertRangeValid(rangeA);
915
- assertRangeValid(rangeB);
916
-
917
- if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
918
- throw new DOMException("WRONG_DOCUMENT_ERR");
919
- }
920
-
921
- var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
922
- endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
923
-
924
- return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
925
- }
926
-
927
- function cloneSubtree(iterator) {
928
- var partiallySelected;
929
- for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
930
- partiallySelected = iterator.isPartiallySelectedSubtree();
931
- node = node.cloneNode(!partiallySelected);
932
- if (partiallySelected) {
933
- subIterator = iterator.getSubtreeIterator();
934
- node.appendChild(cloneSubtree(subIterator));
935
- subIterator.detach(true);
936
- }
937
-
938
- if (node.nodeType == 10) { // DocumentType
939
- throw new DOMException("HIERARCHY_REQUEST_ERR");
940
- }
941
- frag.appendChild(node);
942
- }
943
- return frag;
944
- }
945
-
946
- function iterateSubtree(rangeIterator, func, iteratorState) {
947
- var it, n;
948
- iteratorState = iteratorState || { stop: false };
949
- for (var node, subRangeIterator; node = rangeIterator.next(); ) {
950
- if (rangeIterator.isPartiallySelectedSubtree()) {
951
- if (func(node) === false) {
952
- iteratorState.stop = true;
953
- return;
954
- } else {
955
- // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
956
- // the node selected by the Range.
957
- subRangeIterator = rangeIterator.getSubtreeIterator();
958
- iterateSubtree(subRangeIterator, func, iteratorState);
959
- subRangeIterator.detach(true);
960
- if (iteratorState.stop) {
961
- return;
962
- }
963
- }
964
- } else {
965
- // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
966
- // descendants
967
- it = dom.createIterator(node);
968
- while ( (n = it.next()) ) {
969
- if (func(n) === false) {
970
- iteratorState.stop = true;
971
- return;
972
- }
973
- }
974
- }
975
- }
976
- }
977
-
978
- function deleteSubtree(iterator) {
979
- var subIterator;
980
- while (iterator.next()) {
981
- if (iterator.isPartiallySelectedSubtree()) {
982
- subIterator = iterator.getSubtreeIterator();
983
- deleteSubtree(subIterator);
984
- subIterator.detach(true);
985
- } else {
986
- iterator.remove();
987
- }
988
- }
989
- }
990
-
991
- function extractSubtree(iterator) {
992
- for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
993
-
994
- if (iterator.isPartiallySelectedSubtree()) {
995
- node = node.cloneNode(false);
996
- subIterator = iterator.getSubtreeIterator();
997
- node.appendChild(extractSubtree(subIterator));
998
- subIterator.detach(true);
999
- } else {
1000
- iterator.remove();
1001
- }
1002
- if (node.nodeType == 10) { // DocumentType
1003
- throw new DOMException("HIERARCHY_REQUEST_ERR");
1004
- }
1005
- frag.appendChild(node);
1006
- }
1007
- return frag;
1008
- }
1009
-
1010
- function getNodesInRange(range, nodeTypes, filter) {
1011
- var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
1012
- var filterExists = !!filter;
1013
- if (filterNodeTypes) {
1014
- regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
1015
- }
1016
-
1017
- var nodes = [];
1018
- iterateSubtree(new RangeIterator(range, false), function(node) {
1019
- if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
1020
- nodes.push(node);
1021
- }
1022
- });
1023
- return nodes;
1024
- }
1025
-
1026
- function inspect(range) {
1027
- var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
1028
- return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
1029
- dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
1030
- }
1031
-
1032
- /*----------------------------------------------------------------------------------------------------------------*/
1033
-
1034
- // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
1035
-
1036
- function RangeIterator(range, clonePartiallySelectedTextNodes) {
1037
- this.range = range;
1038
- this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
1039
-
1040
-
1041
- if (!range.collapsed) {
1042
- this.sc = range.startContainer;
1043
- this.so = range.startOffset;
1044
- this.ec = range.endContainer;
1045
- this.eo = range.endOffset;
1046
- var root = range.commonAncestorContainer;
1047
-
1048
- if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
1049
- this.isSingleCharacterDataNode = true;
1050
- this._first = this._last = this._next = this.sc;
1051
- } else {
1052
- this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
1053
- this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
1054
- this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
1055
- this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
1056
- }
1057
- }
1058
- }
1059
-
1060
- RangeIterator.prototype = {
1061
- _current: null,
1062
- _next: null,
1063
- _first: null,
1064
- _last: null,
1065
- isSingleCharacterDataNode: false,
1066
-
1067
- reset: function() {
1068
- this._current = null;
1069
- this._next = this._first;
1070
- },
1071
-
1072
- hasNext: function() {
1073
- return !!this._next;
1074
- },
1075
-
1076
- next: function() {
1077
- // Move to next node
1078
- var current = this._current = this._next;
1079
- if (current) {
1080
- this._next = (current !== this._last) ? current.nextSibling : null;
1081
-
1082
- // Check for partially selected text nodes
1083
- if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
1084
- if (current === this.ec) {
1085
- (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
1086
- }
1087
- if (this._current === this.sc) {
1088
- (current = current.cloneNode(true)).deleteData(0, this.so);
1089
- }
1090
- }
1091
- }
1092
-
1093
- return current;
1094
- },
1095
-
1096
- remove: function() {
1097
- var current = this._current, start, end;
1098
-
1099
- if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
1100
- start = (current === this.sc) ? this.so : 0;
1101
- end = (current === this.ec) ? this.eo : current.length;
1102
- if (start != end) {
1103
- current.deleteData(start, end - start);
1104
- }
1105
- } else {
1106
- if (current.parentNode) {
1107
- current.parentNode.removeChild(current);
1108
- } else {
1109
- }
1110
- }
1111
- },
1112
-
1113
- // Checks if the current node is partially selected
1114
- isPartiallySelectedSubtree: function() {
1115
- var current = this._current;
1116
- return isNonTextPartiallySelected(current, this.range);
1117
- },
1118
-
1119
- getSubtreeIterator: function() {
1120
- var subRange;
1121
- if (this.isSingleCharacterDataNode) {
1122
- subRange = this.range.cloneRange();
1123
- subRange.collapse(false);
1124
- } else {
1125
- subRange = new Range(getRangeDocument(this.range));
1126
- var current = this._current;
1127
- var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
1128
-
1129
- if (isOrIsAncestorOf(current, this.sc)) {
1130
- startContainer = this.sc;
1131
- startOffset = this.so;
1132
- }
1133
- if (isOrIsAncestorOf(current, this.ec)) {
1134
- endContainer = this.ec;
1135
- endOffset = this.eo;
1136
- }
1137
-
1138
- updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
1139
- }
1140
- return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
1141
- },
1142
-
1143
- detach: function(detachRange) {
1144
- if (detachRange) {
1145
- this.range.detach();
1146
- }
1147
- this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
1148
- }
1149
- };
1150
-
1151
- /*----------------------------------------------------------------------------------------------------------------*/
1152
-
1153
- // Exceptions
1154
-
1155
- function RangeException(codeName) {
1156
- this.code = this[codeName];
1157
- this.codeName = codeName;
1158
- this.message = "RangeException: " + this.codeName;
1159
- }
1160
-
1161
- RangeException.prototype = {
1162
- BAD_BOUNDARYPOINTS_ERR: 1,
1163
- INVALID_NODE_TYPE_ERR: 2
1164
- };
1165
-
1166
- RangeException.prototype.toString = function() {
1167
- return this.message;
1168
- };
1169
-
1170
- /*----------------------------------------------------------------------------------------------------------------*/
1171
-
1172
- var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
1173
- var rootContainerNodeTypes = [2, 9, 11];
1174
- var readonlyNodeTypes = [5, 6, 10, 12];
1175
- var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
1176
- var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
1177
-
1178
- function createAncestorFinder(nodeTypes) {
1179
- return function(node, selfIsAncestor) {
1180
- var t, n = selfIsAncestor ? node : node.parentNode;
1181
- while (n) {
1182
- t = n.nodeType;
1183
- if (arrayContains(nodeTypes, t)) {
1184
- return n;
1185
- }
1186
- n = n.parentNode;
1187
- }
1188
- return null;
1189
- };
1190
- }
1191
-
1192
- var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
1193
- var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
1194
- var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
1195
-
1196
- function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
1197
- if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
1198
- throw new RangeException("INVALID_NODE_TYPE_ERR");
1199
- }
1200
- }
1201
-
1202
- function assertNotDetached(range) {
1203
- if (!range.startContainer) {
1204
- throw new DOMException("INVALID_STATE_ERR");
1205
- }
1206
- }
1207
-
1208
- function assertValidNodeType(node, invalidTypes) {
1209
- if (!arrayContains(invalidTypes, node.nodeType)) {
1210
- throw new RangeException("INVALID_NODE_TYPE_ERR");
1211
- }
1212
- }
1213
-
1214
- function assertValidOffset(node, offset) {
1215
- if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
1216
- throw new DOMException("INDEX_SIZE_ERR");
1217
- }
1218
- }
1219
-
1220
- function assertSameDocumentOrFragment(node1, node2) {
1221
- if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
1222
- throw new DOMException("WRONG_DOCUMENT_ERR");
1223
- }
1224
- }
1225
-
1226
- function assertNodeNotReadOnly(node) {
1227
- if (getReadonlyAncestor(node, true)) {
1228
- throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
1229
- }
1230
- }
1231
-
1232
- function assertNode(node, codeName) {
1233
- if (!node) {
1234
- throw new DOMException(codeName);
1235
- }
1236
- }
1237
-
1238
- function isOrphan(node) {
1239
- return (crashyTextNodes && dom.isBrokenNode(node)) ||
1240
- !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
1241
- }
1242
-
1243
- function isValidOffset(node, offset) {
1244
- return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
1245
- }
1246
-
1247
- function isRangeValid(range) {
1248
- return (!!range.startContainer && !!range.endContainer
1249
- && !isOrphan(range.startContainer)
1250
- && !isOrphan(range.endContainer)
1251
- && isValidOffset(range.startContainer, range.startOffset)
1252
- && isValidOffset(range.endContainer, range.endOffset));
1253
- }
1254
-
1255
- function assertRangeValid(range) {
1256
- assertNotDetached(range);
1257
- if (!isRangeValid(range)) {
1258
- throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
1259
- }
1260
- }
1261
-
1262
- /*----------------------------------------------------------------------------------------------------------------*/
1263
-
1264
- // Test the browser's innerHTML support to decide how to implement createContextualFragment
1265
- var styleEl = document.createElement("style");
1266
- var htmlParsingConforms = false;
1267
- try {
1268
- styleEl.innerHTML = "<b>x</b>";
1269
- htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
1270
- } catch (e) {
1271
- // IE 6 and 7 throw
1272
- }
1273
-
1274
- api.features.htmlParsingConforms = htmlParsingConforms;
1275
-
1276
- var createContextualFragment = htmlParsingConforms ?
1277
-
1278
- // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
1279
- // discussion and base code for this implementation at issue 67.
1280
- // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
1281
- // Thanks to Aleks Williams.
1282
- function(fragmentStr) {
1283
- // "Let node the context object's start's node."
1284
- var node = this.startContainer;
1285
- var doc = getDocument(node);
1286
-
1287
- // "If the context object's start's node is null, raise an INVALID_STATE_ERR
1288
- // exception and abort these steps."
1289
- if (!node) {
1290
- throw new DOMException("INVALID_STATE_ERR");
1291
- }
1292
-
1293
- // "Let element be as follows, depending on node's interface:"
1294
- // Document, Document Fragment: null
1295
- var el = null;
1296
-
1297
- // "Element: node"
1298
- if (node.nodeType == 1) {
1299
- el = node;
1300
-
1301
- // "Text, Comment: node's parentElement"
1302
- } else if (isCharacterDataNode(node)) {
1303
- el = dom.parentElement(node);
1304
- }
1305
-
1306
- // "If either element is null or element's ownerDocument is an HTML document
1307
- // and element's local name is "html" and element's namespace is the HTML
1308
- // namespace"
1309
- if (el === null || (
1310
- el.nodeName == "HTML"
1311
- && dom.isHtmlNamespace(getDocument(el).documentElement)
1312
- && dom.isHtmlNamespace(el)
1313
- )) {
1314
-
1315
- // "let element be a new Element with "body" as its local name and the HTML
1316
- // namespace as its namespace.""
1317
- el = doc.createElement("body");
1318
- } else {
1319
- el = el.cloneNode(false);
1320
- }
1321
-
1322
- // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
1323
- // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
1324
- // "In either case, the algorithm must be invoked with fragment as the input
1325
- // and element as the context element."
1326
- el.innerHTML = fragmentStr;
1327
-
1328
- // "If this raises an exception, then abort these steps. Otherwise, let new
1329
- // children be the nodes returned."
1330
-
1331
- // "Let fragment be a new DocumentFragment."
1332
- // "Append all new children to fragment."
1333
- // "Return fragment."
1334
- return dom.fragmentFromNodeChildren(el);
1335
- } :
1336
-
1337
- // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
1338
- // previous versions of Rangy used (with the exception of using a body element rather than a div)
1339
- function(fragmentStr) {
1340
- assertNotDetached(this);
1341
- var doc = getRangeDocument(this);
1342
- var el = doc.createElement("body");
1343
- el.innerHTML = fragmentStr;
1344
-
1345
- return dom.fragmentFromNodeChildren(el);
1346
- };
1347
-
1348
- function splitRangeBoundaries(range, positionsToPreserve) {
1349
- assertRangeValid(range);
1350
-
1351
- var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
1352
- var startEndSame = (sc === ec);
1353
-
1354
- if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
1355
- splitDataNode(ec, eo, positionsToPreserve);
1356
- }
1357
-
1358
- if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
1359
- sc = splitDataNode(sc, so, positionsToPreserve);
1360
- if (startEndSame) {
1361
- eo -= so;
1362
- ec = sc;
1363
- } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
1364
- eo++;
1365
- }
1366
- so = 0;
1367
- }
1368
- range.setStartAndEnd(sc, so, ec, eo);
1369
- }
1370
-
1371
- /*----------------------------------------------------------------------------------------------------------------*/
1372
-
1373
- var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
1374
- "commonAncestorContainer"];
1375
-
1376
- var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
1377
- var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
1378
-
1379
- function RangePrototype() {}
1380
-
1381
- RangePrototype.prototype = {
1382
- compareBoundaryPoints: function(how, range) {
1383
- assertRangeValid(this);
1384
- assertSameDocumentOrFragment(this.startContainer, range.startContainer);
1385
-
1386
- var nodeA, offsetA, nodeB, offsetB;
1387
- var prefixA = (how == e2s || how == s2s) ? "start" : "end";
1388
- var prefixB = (how == s2e || how == s2s) ? "start" : "end";
1389
- nodeA = this[prefixA + "Container"];
1390
- offsetA = this[prefixA + "Offset"];
1391
- nodeB = range[prefixB + "Container"];
1392
- offsetB = range[prefixB + "Offset"];
1393
- return comparePoints(nodeA, offsetA, nodeB, offsetB);
1394
- },
1395
-
1396
- insertNode: function(node) {
1397
- assertRangeValid(this);
1398
- assertValidNodeType(node, insertableNodeTypes);
1399
- assertNodeNotReadOnly(this.startContainer);
1400
-
1401
- if (isOrIsAncestorOf(node, this.startContainer)) {
1402
- throw new DOMException("HIERARCHY_REQUEST_ERR");
1403
- }
1404
-
1405
- // No check for whether the container of the start of the Range is of a type that does not allow
1406
- // children of the type of node: the browser's DOM implementation should do this for us when we attempt
1407
- // to add the node
1408
-
1409
- var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
1410
- this.setStartBefore(firstNodeInserted);
1411
- },
1412
-
1413
- cloneContents: function() {
1414
- assertRangeValid(this);
1415
-
1416
- var clone, frag;
1417
- if (this.collapsed) {
1418
- return getRangeDocument(this).createDocumentFragment();
1419
- } else {
1420
- if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
1421
- clone = this.startContainer.cloneNode(true);
1422
- clone.data = clone.data.slice(this.startOffset, this.endOffset);
1423
- frag = getRangeDocument(this).createDocumentFragment();
1424
- frag.appendChild(clone);
1425
- return frag;
1426
- } else {
1427
- var iterator = new RangeIterator(this, true);
1428
- clone = cloneSubtree(iterator);
1429
- iterator.detach();
1430
- }
1431
- return clone;
1432
- }
1433
- },
1434
-
1435
- canSurroundContents: function() {
1436
- assertRangeValid(this);
1437
- assertNodeNotReadOnly(this.startContainer);
1438
- assertNodeNotReadOnly(this.endContainer);
1439
-
1440
- // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
1441
- // no non-text nodes.
1442
- var iterator = new RangeIterator(this, true);
1443
- var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
1444
- (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
1445
- iterator.detach();
1446
- return !boundariesInvalid;
1447
- },
1448
-
1449
- surroundContents: function(node) {
1450
- assertValidNodeType(node, surroundNodeTypes);
1451
-
1452
- if (!this.canSurroundContents()) {
1453
- throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
1454
- }
1455
-
1456
- // Extract the contents
1457
- var content = this.extractContents();
1458
-
1459
- // Clear the children of the node
1460
- if (node.hasChildNodes()) {
1461
- while (node.lastChild) {
1462
- node.removeChild(node.lastChild);
1463
- }
1464
- }
1465
-
1466
- // Insert the new node and add the extracted contents
1467
- insertNodeAtPosition(node, this.startContainer, this.startOffset);
1468
- node.appendChild(content);
1469
-
1470
- this.selectNode(node);
1471
- },
1472
-
1473
- cloneRange: function() {
1474
- assertRangeValid(this);
1475
- var range = new Range(getRangeDocument(this));
1476
- var i = rangeProperties.length, prop;
1477
- while (i--) {
1478
- prop = rangeProperties[i];
1479
- range[prop] = this[prop];
1480
- }
1481
- return range;
1482
- },
1483
-
1484
- toString: function() {
1485
- assertRangeValid(this);
1486
- var sc = this.startContainer;
1487
- if (sc === this.endContainer && isCharacterDataNode(sc)) {
1488
- return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
1489
- } else {
1490
- var textBits = [], iterator = new RangeIterator(this, true);
1491
- iterateSubtree(iterator, function(node) {
1492
- // Accept only text or CDATA nodes, not comments
1493
- if (node.nodeType == 3 || node.nodeType == 4) {
1494
- textBits.push(node.data);
1495
- }
1496
- });
1497
- iterator.detach();
1498
- return textBits.join("");
1499
- }
1500
- },
1501
-
1502
- // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
1503
- // been removed from Mozilla.
1504
-
1505
- compareNode: function(node) {
1506
- assertRangeValid(this);
1507
-
1508
- var parent = node.parentNode;
1509
- var nodeIndex = getNodeIndex(node);
1510
-
1511
- if (!parent) {
1512
- throw new DOMException("NOT_FOUND_ERR");
1513
- }
1514
-
1515
- var startComparison = this.comparePoint(parent, nodeIndex),
1516
- endComparison = this.comparePoint(parent, nodeIndex + 1);
1517
-
1518
- if (startComparison < 0) { // Node starts before
1519
- return (endComparison > 0) ? n_b_a : n_b;
1520
- } else {
1521
- return (endComparison > 0) ? n_a : n_i;
1522
- }
1523
- },
1524
-
1525
- comparePoint: function(node, offset) {
1526
- assertRangeValid(this);
1527
- assertNode(node, "HIERARCHY_REQUEST_ERR");
1528
- assertSameDocumentOrFragment(node, this.startContainer);
1529
-
1530
- if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
1531
- return -1;
1532
- } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
1533
- return 1;
1534
- }
1535
- return 0;
1536
- },
1537
-
1538
- createContextualFragment: createContextualFragment,
1539
-
1540
- toHtml: function() {
1541
- assertRangeValid(this);
1542
- var container = this.commonAncestorContainer.parentNode.cloneNode(false);
1543
- container.appendChild(this.cloneContents());
1544
- return container.innerHTML;
1545
- },
1546
-
1547
- // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
1548
- // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
1549
- intersectsNode: function(node, touchingIsIntersecting) {
1550
- assertRangeValid(this);
1551
- assertNode(node, "NOT_FOUND_ERR");
1552
- if (getDocument(node) !== getRangeDocument(this)) {
1553
- return false;
1554
- }
1555
-
1556
- var parent = node.parentNode, offset = getNodeIndex(node);
1557
- assertNode(parent, "NOT_FOUND_ERR");
1558
-
1559
- var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
1560
- endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
1561
-
1562
- return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1563
- },
1564
-
1565
- isPointInRange: function(node, offset) {
1566
- assertRangeValid(this);
1567
- assertNode(node, "HIERARCHY_REQUEST_ERR");
1568
- assertSameDocumentOrFragment(node, this.startContainer);
1569
-
1570
- return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
1571
- (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
1572
- },
1573
-
1574
- // The methods below are non-standard and invented by me.
1575
-
1576
- // Sharing a boundary start-to-end or end-to-start does not count as intersection.
1577
- intersectsRange: function(range) {
1578
- return rangesIntersect(this, range, false);
1579
- },
1580
-
1581
- // Sharing a boundary start-to-end or end-to-start does count as intersection.
1582
- intersectsOrTouchesRange: function(range) {
1583
- return rangesIntersect(this, range, true);
1584
- },
1585
-
1586
- intersection: function(range) {
1587
- if (this.intersectsRange(range)) {
1588
- var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
1589
- endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
1590
-
1591
- var intersectionRange = this.cloneRange();
1592
- if (startComparison == -1) {
1593
- intersectionRange.setStart(range.startContainer, range.startOffset);
1594
- }
1595
- if (endComparison == 1) {
1596
- intersectionRange.setEnd(range.endContainer, range.endOffset);
1597
- }
1598
- return intersectionRange;
1599
- }
1600
- return null;
1601
- },
1602
-
1603
- union: function(range) {
1604
- if (this.intersectsOrTouchesRange(range)) {
1605
- var unionRange = this.cloneRange();
1606
- if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
1607
- unionRange.setStart(range.startContainer, range.startOffset);
1608
- }
1609
- if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
1610
- unionRange.setEnd(range.endContainer, range.endOffset);
1611
- }
1612
- return unionRange;
1613
- } else {
1614
- throw new RangeException("Ranges do not intersect");
1615
- }
1616
- },
1617
-
1618
- containsNode: function(node, allowPartial) {
1619
- if (allowPartial) {
1620
- return this.intersectsNode(node, false);
1621
- } else {
1622
- return this.compareNode(node) == n_i;
1623
- }
1624
- },
1625
-
1626
- containsNodeContents: function(node) {
1627
- return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
1628
- },
1629
-
1630
- containsRange: function(range) {
1631
- var intersection = this.intersection(range);
1632
- return intersection !== null && range.equals(intersection);
1633
- },
1634
-
1635
- containsNodeText: function(node) {
1636
- var nodeRange = this.cloneRange();
1637
- nodeRange.selectNode(node);
1638
- var textNodes = nodeRange.getNodes([3]);
1639
- if (textNodes.length > 0) {
1640
- nodeRange.setStart(textNodes[0], 0);
1641
- var lastTextNode = textNodes.pop();
1642
- nodeRange.setEnd(lastTextNode, lastTextNode.length);
1643
- var contains = this.containsRange(nodeRange);
1644
- nodeRange.detach();
1645
- return contains;
1646
- } else {
1647
- return this.containsNodeContents(node);
1648
- }
1649
- },
1650
-
1651
- getNodes: function(nodeTypes, filter) {
1652
- assertRangeValid(this);
1653
- return getNodesInRange(this, nodeTypes, filter);
1654
- },
1655
-
1656
- getDocument: function() {
1657
- return getRangeDocument(this);
1658
- },
1659
-
1660
- collapseBefore: function(node) {
1661
- assertNotDetached(this);
1662
-
1663
- this.setEndBefore(node);
1664
- this.collapse(false);
1665
- },
1666
-
1667
- collapseAfter: function(node) {
1668
- assertNotDetached(this);
1669
-
1670
- this.setStartAfter(node);
1671
- this.collapse(true);
1672
- },
1673
-
1674
- getBookmark: function(containerNode) {
1675
- var doc = getRangeDocument(this);
1676
- var preSelectionRange = api.createRange(doc);
1677
- containerNode = containerNode || dom.getBody(doc);
1678
- preSelectionRange.selectNodeContents(containerNode);
1679
- var range = this.intersection(preSelectionRange);
1680
- var start = 0, end = 0;
1681
- if (range) {
1682
- preSelectionRange.setEnd(range.startContainer, range.startOffset);
1683
- start = preSelectionRange.toString().length;
1684
- end = start + range.toString().length;
1685
- preSelectionRange.detach();
1686
- }
1687
-
1688
- return {
1689
- start: start,
1690
- end: end,
1691
- containerNode: containerNode
1692
- };
1693
- },
1694
-
1695
- moveToBookmark: function(bookmark) {
1696
- var containerNode = bookmark.containerNode;
1697
- var charIndex = 0;
1698
- this.setStart(containerNode, 0);
1699
- this.collapse(true);
1700
- var nodeStack = [containerNode], node, foundStart = false, stop = false;
1701
- var nextCharIndex, i, childNodes;
1702
-
1703
- while (!stop && (node = nodeStack.pop())) {
1704
- if (node.nodeType == 3) {
1705
- nextCharIndex = charIndex + node.length;
1706
- if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
1707
- this.setStart(node, bookmark.start - charIndex);
1708
- foundStart = true;
1709
- }
1710
- if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
1711
- this.setEnd(node, bookmark.end - charIndex);
1712
- stop = true;
1713
- }
1714
- charIndex = nextCharIndex;
1715
- } else {
1716
- childNodes = node.childNodes;
1717
- i = childNodes.length;
1718
- while (i--) {
1719
- nodeStack.push(childNodes[i]);
1720
- }
1721
- }
1722
- }
1723
- },
1724
-
1725
- getName: function() {
1726
- return "DomRange";
1727
- },
1728
-
1729
- equals: function(range) {
1730
- return Range.rangesEqual(this, range);
1731
- },
1732
-
1733
- isValid: function() {
1734
- return isRangeValid(this);
1735
- },
1736
-
1737
- inspect: function() {
1738
- return inspect(this);
1739
- }
1740
- };
1741
-
1742
- function copyComparisonConstantsToObject(obj) {
1743
- obj.START_TO_START = s2s;
1744
- obj.START_TO_END = s2e;
1745
- obj.END_TO_END = e2e;
1746
- obj.END_TO_START = e2s;
1747
-
1748
- obj.NODE_BEFORE = n_b;
1749
- obj.NODE_AFTER = n_a;
1750
- obj.NODE_BEFORE_AND_AFTER = n_b_a;
1751
- obj.NODE_INSIDE = n_i;
1752
- }
1753
-
1754
- function copyComparisonConstants(constructor) {
1755
- copyComparisonConstantsToObject(constructor);
1756
- copyComparisonConstantsToObject(constructor.prototype);
1757
- }
1758
-
1759
- function createRangeContentRemover(remover, boundaryUpdater) {
1760
- return function() {
1761
- assertRangeValid(this);
1762
-
1763
- var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
1764
-
1765
- var iterator = new RangeIterator(this, true);
1766
-
1767
- // Work out where to position the range after content removal
1768
- var node, boundary;
1769
- if (sc !== root) {
1770
- node = getClosestAncestorIn(sc, root, true);
1771
- boundary = getBoundaryAfterNode(node);
1772
- sc = boundary.node;
1773
- so = boundary.offset;
1774
- }
1775
-
1776
- // Check none of the range is read-only
1777
- iterateSubtree(iterator, assertNodeNotReadOnly);
1778
-
1779
- iterator.reset();
1780
-
1781
- // Remove the content
1782
- var returnValue = remover(iterator);
1783
- iterator.detach();
1784
-
1785
- // Move to the new position
1786
- boundaryUpdater(this, sc, so, sc, so);
1787
-
1788
- return returnValue;
1789
- };
1790
- }
1791
-
1792
- function createPrototypeRange(constructor, boundaryUpdater, detacher) {
1793
- function createBeforeAfterNodeSetter(isBefore, isStart) {
1794
- return function(node) {
1795
- assertNotDetached(this);
1796
- assertValidNodeType(node, beforeAfterNodeTypes);
1797
- assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
1798
-
1799
- var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
1800
- (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
1801
- };
1802
- }
1803
-
1804
- function setRangeStart(range, node, offset) {
1805
- var ec = range.endContainer, eo = range.endOffset;
1806
- if (node !== range.startContainer || offset !== range.startOffset) {
1807
- // Check the root containers of the range and the new boundary, and also check whether the new boundary
1808
- // is after the current end. In either case, collapse the range to the new position
1809
- if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
1810
- ec = node;
1811
- eo = offset;
1812
- }
1813
- boundaryUpdater(range, node, offset, ec, eo);
1814
- }
1815
- }
1816
-
1817
- function setRangeEnd(range, node, offset) {
1818
- var sc = range.startContainer, so = range.startOffset;
1819
- if (node !== range.endContainer || offset !== range.endOffset) {
1820
- // Check the root containers of the range and the new boundary, and also check whether the new boundary
1821
- // is after the current end. In either case, collapse the range to the new position
1822
- if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
1823
- sc = node;
1824
- so = offset;
1825
- }
1826
- boundaryUpdater(range, sc, so, node, offset);
1827
- }
1828
- }
1829
-
1830
- constructor.prototype = new RangePrototype();
1831
-
1832
- util.extend(constructor.prototype, {
1833
- setStart: function(node, offset) {
1834
- assertNotDetached(this);
1835
- assertNoDocTypeNotationEntityAncestor(node, true);
1836
- assertValidOffset(node, offset);
1837
-
1838
- setRangeStart(this, node, offset);
1839
- },
1840
-
1841
- setEnd: function(node, offset) {
1842
- assertNotDetached(this);
1843
- assertNoDocTypeNotationEntityAncestor(node, true);
1844
- assertValidOffset(node, offset);
1845
-
1846
- setRangeEnd(this, node, offset);
1847
- },
1848
-
1849
- /**
1850
- * Convenience method to set a range's start and end boundaries. Overloaded as follows:
1851
- * - Two parameters (node, offset) creates a collapsed range at that position
1852
- * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
1853
- * startOffset and ending at endOffset
1854
- * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
1855
- * startNode and ending at endOffset in endNode
1856
- */
1857
- setStartAndEnd: function() {
1858
- assertNotDetached(this);
1859
-
1860
- var args = arguments;
1861
- var sc = args[0], so = args[1], ec = sc, eo = so;
1862
-
1863
- switch (args.length) {
1864
- case 3:
1865
- eo = args[2];
1866
- break;
1867
- case 4:
1868
- ec = args[2];
1869
- eo = args[3];
1870
- break;
1871
- }
1872
-
1873
- boundaryUpdater(this, sc, so, ec, eo);
1874
- },
1875
-
1876
- setBoundary: function(node, offset, isStart) {
1877
- this["set" + (isStart ? "Start" : "End")](node, offset);
1878
- },
1879
-
1880
- setStartBefore: createBeforeAfterNodeSetter(true, true),
1881
- setStartAfter: createBeforeAfterNodeSetter(false, true),
1882
- setEndBefore: createBeforeAfterNodeSetter(true, false),
1883
- setEndAfter: createBeforeAfterNodeSetter(false, false),
1884
-
1885
- collapse: function(isStart) {
1886
- assertRangeValid(this);
1887
- if (isStart) {
1888
- boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
1889
- } else {
1890
- boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
1891
- }
1892
- },
1893
-
1894
- selectNodeContents: function(node) {
1895
- assertNotDetached(this);
1896
- assertNoDocTypeNotationEntityAncestor(node, true);
1897
-
1898
- boundaryUpdater(this, node, 0, node, getNodeLength(node));
1899
- },
1900
-
1901
- selectNode: function(node) {
1902
- assertNotDetached(this);
1903
- assertNoDocTypeNotationEntityAncestor(node, false);
1904
- assertValidNodeType(node, beforeAfterNodeTypes);
1905
-
1906
- var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
1907
- boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
1908
- },
1909
-
1910
- extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
1911
-
1912
- deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
1913
-
1914
- canSurroundContents: function() {
1915
- assertRangeValid(this);
1916
- assertNodeNotReadOnly(this.startContainer);
1917
- assertNodeNotReadOnly(this.endContainer);
1918
-
1919
- // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
1920
- // no non-text nodes.
1921
- var iterator = new RangeIterator(this, true);
1922
- var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
1923
- (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
1924
- iterator.detach();
1925
- return !boundariesInvalid;
1926
- },
1927
-
1928
- detach: function() {
1929
- detacher(this);
1930
- },
1931
-
1932
- splitBoundaries: function() {
1933
- splitRangeBoundaries(this);
1934
- },
1935
-
1936
- splitBoundariesPreservingPositions: function(positionsToPreserve) {
1937
- splitRangeBoundaries(this, positionsToPreserve);
1938
- },
1939
-
1940
- normalizeBoundaries: function() {
1941
- assertRangeValid(this);
1942
-
1943
- var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
1944
-
1945
- var mergeForward = function(node) {
1946
- var sibling = node.nextSibling;
1947
- if (sibling && sibling.nodeType == node.nodeType) {
1948
- ec = node;
1949
- eo = node.length;
1950
- node.appendData(sibling.data);
1951
- sibling.parentNode.removeChild(sibling);
1952
- }
1953
- };
1954
-
1955
- var mergeBackward = function(node) {
1956
- var sibling = node.previousSibling;
1957
- if (sibling && sibling.nodeType == node.nodeType) {
1958
- sc = node;
1959
- var nodeLength = node.length;
1960
- so = sibling.length;
1961
- node.insertData(0, sibling.data);
1962
- sibling.parentNode.removeChild(sibling);
1963
- if (sc == ec) {
1964
- eo += so;
1965
- ec = sc;
1966
- } else if (ec == node.parentNode) {
1967
- var nodeIndex = getNodeIndex(node);
1968
- if (eo == nodeIndex) {
1969
- ec = node;
1970
- eo = nodeLength;
1971
- } else if (eo > nodeIndex) {
1972
- eo--;
1973
- }
1974
- }
1975
- }
1976
- };
1977
-
1978
- var normalizeStart = true;
1979
-
1980
- if (isCharacterDataNode(ec)) {
1981
- if (ec.length == eo) {
1982
- mergeForward(ec);
1983
- }
1984
- } else {
1985
- if (eo > 0) {
1986
- var endNode = ec.childNodes[eo - 1];
1987
- if (endNode && isCharacterDataNode(endNode)) {
1988
- mergeForward(endNode);
1989
- }
1990
- }
1991
- normalizeStart = !this.collapsed;
1992
- }
1993
-
1994
- if (normalizeStart) {
1995
- if (isCharacterDataNode(sc)) {
1996
- if (so == 0) {
1997
- mergeBackward(sc);
1998
- }
1999
- } else {
2000
- if (so < sc.childNodes.length) {
2001
- var startNode = sc.childNodes[so];
2002
- if (startNode && isCharacterDataNode(startNode)) {
2003
- mergeBackward(startNode);
2004
- }
2005
- }
2006
- }
2007
- } else {
2008
- sc = ec;
2009
- so = eo;
2010
- }
2011
-
2012
- boundaryUpdater(this, sc, so, ec, eo);
2013
- },
2014
-
2015
- collapseToPoint: function(node, offset) {
2016
- assertNotDetached(this);
2017
- assertNoDocTypeNotationEntityAncestor(node, true);
2018
- assertValidOffset(node, offset);
2019
- this.setStartAndEnd(node, offset);
2020
- }
2021
- });
2022
-
2023
- copyComparisonConstants(constructor);
2024
- }
2025
-
2026
- /*----------------------------------------------------------------------------------------------------------------*/
2027
-
2028
- // Updates commonAncestorContainer and collapsed after boundary change
2029
- function updateCollapsedAndCommonAncestor(range) {
2030
- range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2031
- range.commonAncestorContainer = range.collapsed ?
2032
- range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
2033
- }
2034
-
2035
- function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
2036
- range.startContainer = startContainer;
2037
- range.startOffset = startOffset;
2038
- range.endContainer = endContainer;
2039
- range.endOffset = endOffset;
2040
- range.document = dom.getDocument(startContainer);
2041
-
2042
- updateCollapsedAndCommonAncestor(range);
2043
- }
2044
-
2045
- function detach(range) {
2046
- assertNotDetached(range);
2047
- range.startContainer = range.startOffset = range.endContainer = range.endOffset = range.document = null;
2048
- range.collapsed = range.commonAncestorContainer = null;
2049
- }
2050
-
2051
- function Range(doc) {
2052
- this.startContainer = doc;
2053
- this.startOffset = 0;
2054
- this.endContainer = doc;
2055
- this.endOffset = 0;
2056
- this.document = doc;
2057
- updateCollapsedAndCommonAncestor(this);
2058
- }
2059
-
2060
- createPrototypeRange(Range, updateBoundaries, detach);
2061
-
2062
- api.rangePrototype = RangePrototype.prototype;
2063
-
2064
- util.extend(Range, {
2065
- rangeProperties: rangeProperties,
2066
- RangeIterator: RangeIterator,
2067
- copyComparisonConstants: copyComparisonConstants,
2068
- createPrototypeRange: createPrototypeRange,
2069
- inspect: inspect,
2070
- getRangeDocument: getRangeDocument,
2071
- rangesEqual: function(r1, r2) {
2072
- return r1.startContainer === r2.startContainer &&
2073
- r1.startOffset === r2.startOffset &&
2074
- r1.endContainer === r2.endContainer &&
2075
- r1.endOffset === r2.endOffset;
2076
- }
2077
- });
2078
-
2079
- api.DomRange = Range;
2080
- api.RangeException = RangeException;
2081
- });
2082
- rangy.createModule("WrappedRange", function(api, module) {
2083
- api.requireModules( ["DomUtil", "DomRange"] );
2084
-
2085
- var WrappedRange, WrappedTextRange;
2086
- var dom = api.dom;
2087
- var util = api.util;
2088
- var DomPosition = dom.DomPosition;
2089
- var DomRange = api.DomRange;
2090
- var getBody = dom.getBody;
2091
- var getContentDocument = dom.getContentDocument;
2092
- var isCharacterDataNode = dom.isCharacterDataNode;
2093
-
2094
-
2095
- /*----------------------------------------------------------------------------------------------------------------*/
2096
-
2097
- if (api.features.implementsDomRange) {
2098
- // This is a wrapper around the browser's native DOM Range. It has two aims:
2099
- // - Provide workarounds for specific browser bugs
2100
- // - provide convenient extensions, which are inherited from Rangy's DomRange
2101
-
2102
- (function() {
2103
- var rangeProto;
2104
- var rangeProperties = DomRange.rangeProperties;
2105
-
2106
- function updateRangeProperties(range) {
2107
- var i = rangeProperties.length, prop;
2108
- while (i--) {
2109
- prop = rangeProperties[i];
2110
- range[prop] = range.nativeRange[prop];
2111
- }
2112
- // Fix for broken collapsed property in IE 9.
2113
- range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2114
- }
2115
-
2116
- function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
2117
- var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
2118
- var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
2119
- var nativeRangeDifferent = !range.equals(range.nativeRange);
2120
-
2121
- // Always set both boundaries for the benefit of IE9 (see issue 35)
2122
- if (startMoved || endMoved || nativeRangeDifferent) {
2123
- range.setEnd(endContainer, endOffset);
2124
- range.setStart(startContainer, startOffset);
2125
- }
2126
- }
2127
-
2128
- function detach(range) {
2129
- range.nativeRange.detach();
2130
- range.detached = true;
2131
- var i = rangeProperties.length;
2132
- while (i--) {
2133
- range[ rangeProperties[i] ] = null;
2134
- }
2135
- }
2136
-
2137
- var createBeforeAfterNodeSetter;
2138
-
2139
- WrappedRange = function(range) {
2140
- if (!range) {
2141
- throw module.createError("WrappedRange: Range must be specified");
2142
- }
2143
- this.nativeRange = range;
2144
- updateRangeProperties(this);
2145
- };
2146
-
2147
- DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
2148
-
2149
- rangeProto = WrappedRange.prototype;
2150
-
2151
- rangeProto.selectNode = function(node) {
2152
- this.nativeRange.selectNode(node);
2153
- updateRangeProperties(this);
2154
- };
2155
-
2156
- rangeProto.cloneContents = function() {
2157
- return this.nativeRange.cloneContents();
2158
- };
2159
-
2160
- // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
2161
- // insertNode() is never delegated to the native range.
2162
-
2163
- rangeProto.surroundContents = function(node) {
2164
- this.nativeRange.surroundContents(node);
2165
- updateRangeProperties(this);
2166
- };
2167
-
2168
- rangeProto.collapse = function(isStart) {
2169
- this.nativeRange.collapse(isStart);
2170
- updateRangeProperties(this);
2171
- };
2172
-
2173
- rangeProto.cloneRange = function() {
2174
- return new WrappedRange(this.nativeRange.cloneRange());
2175
- };
2176
-
2177
- rangeProto.refresh = function() {
2178
- updateRangeProperties(this);
2179
- };
2180
-
2181
- rangeProto.toString = function() {
2182
- return this.nativeRange.toString();
2183
- };
2184
-
2185
- // Create test range and node for feature detection
2186
-
2187
- var testTextNode = document.createTextNode("test");
2188
- getBody(document).appendChild(testTextNode);
2189
- var range = document.createRange();
2190
-
2191
- /*--------------------------------------------------------------------------------------------------------*/
2192
-
2193
- // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
2194
- // correct for it
2195
-
2196
- range.setStart(testTextNode, 0);
2197
- range.setEnd(testTextNode, 0);
2198
-
2199
- try {
2200
- range.setStart(testTextNode, 1);
2201
-
2202
- rangeProto.setStart = function(node, offset) {
2203
- this.nativeRange.setStart(node, offset);
2204
- updateRangeProperties(this);
2205
- };
2206
-
2207
- rangeProto.setEnd = function(node, offset) {
2208
- this.nativeRange.setEnd(node, offset);
2209
- updateRangeProperties(this);
2210
- };
2211
-
2212
- createBeforeAfterNodeSetter = function(name) {
2213
- return function(node) {
2214
- this.nativeRange[name](node);
2215
- updateRangeProperties(this);
2216
- };
2217
- };
2218
-
2219
- } catch(ex) {
2220
-
2221
- rangeProto.setStart = function(node, offset) {
2222
- try {
2223
- this.nativeRange.setStart(node, offset);
2224
- } catch (ex) {
2225
- this.nativeRange.setEnd(node, offset);
2226
- this.nativeRange.setStart(node, offset);
2227
- }
2228
- updateRangeProperties(this);
2229
- };
2230
-
2231
- rangeProto.setEnd = function(node, offset) {
2232
- try {
2233
- this.nativeRange.setEnd(node, offset);
2234
- } catch (ex) {
2235
- this.nativeRange.setStart(node, offset);
2236
- this.nativeRange.setEnd(node, offset);
2237
- }
2238
- updateRangeProperties(this);
2239
- };
2240
-
2241
- createBeforeAfterNodeSetter = function(name, oppositeName) {
2242
- return function(node) {
2243
- try {
2244
- this.nativeRange[name](node);
2245
- } catch (ex) {
2246
- this.nativeRange[oppositeName](node);
2247
- this.nativeRange[name](node);
2248
- }
2249
- updateRangeProperties(this);
2250
- };
2251
- };
2252
- }
2253
-
2254
- rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
2255
- rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
2256
- rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
2257
- rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
2258
-
2259
- /*--------------------------------------------------------------------------------------------------------*/
2260
-
2261
- // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
2262
- // the 0th character of the text node
2263
- range.selectNodeContents(testTextNode);
2264
- if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
2265
- range.startOffset == 0 && range.endOffset == testTextNode.length) {
2266
- rangeProto.selectNodeContents = function(node) {
2267
- this.nativeRange.selectNodeContents(node);
2268
- updateRangeProperties(this);
2269
- };
2270
- } else {
2271
- rangeProto.selectNodeContents = function(node) {
2272
- this.setStartAndEnd(node, 0, dom.getNodeLength(node));
2273
- };
2274
- }
2275
-
2276
- /*--------------------------------------------------------------------------------------------------------*/
2277
-
2278
- // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
2279
- // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
2280
-
2281
- range.selectNodeContents(testTextNode);
2282
- range.setEnd(testTextNode, 3);
2283
-
2284
- var range2 = document.createRange();
2285
- range2.selectNodeContents(testTextNode);
2286
- range2.setEnd(testTextNode, 4);
2287
- range2.setStart(testTextNode, 2);
2288
-
2289
- if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
2290
- range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
2291
- // This is the wrong way round, so correct for it
2292
-
2293
- rangeProto.compareBoundaryPoints = function(type, range) {
2294
- range = range.nativeRange || range;
2295
- if (type == range.START_TO_END) {
2296
- type = range.END_TO_START;
2297
- } else if (type == range.END_TO_START) {
2298
- type = range.START_TO_END;
2299
- }
2300
- return this.nativeRange.compareBoundaryPoints(type, range);
2301
- };
2302
- } else {
2303
- rangeProto.compareBoundaryPoints = function(type, range) {
2304
- return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
2305
- };
2306
- }
2307
-
2308
- /*--------------------------------------------------------------------------------------------------------*/
2309
-
2310
- // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107.
2311
-
2312
- var el = document.createElement("div");
2313
- el.innerHTML = "123";
2314
- var textNode = el.firstChild;
2315
- var body = getBody(document);
2316
- body.appendChild(el);
2317
-
2318
- range.setStart(textNode, 1);
2319
- range.setEnd(textNode, 2);
2320
- range.deleteContents();
2321
-
2322
- if (textNode.data == "13") {
2323
- // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
2324
- // extractContents()
2325
- rangeProto.deleteContents = function() {
2326
- this.nativeRange.deleteContents();
2327
- updateRangeProperties(this);
2328
- };
2329
-
2330
- rangeProto.extractContents = function() {
2331
- var frag = this.nativeRange.extractContents();
2332
- updateRangeProperties(this);
2333
- return frag;
2334
- };
2335
- } else {
2336
- }
2337
-
2338
- body.removeChild(el);
2339
- body = null;
2340
-
2341
- /*--------------------------------------------------------------------------------------------------------*/
2342
-
2343
- // Test for existence of createContextualFragment and delegate to it if it exists
2344
- if (util.isHostMethod(range, "createContextualFragment")) {
2345
- rangeProto.createContextualFragment = function(fragmentStr) {
2346
- return this.nativeRange.createContextualFragment(fragmentStr);
2347
- };
2348
- }
2349
-
2350
- /*--------------------------------------------------------------------------------------------------------*/
2351
-
2352
- // Clean up
2353
- getBody(document).removeChild(testTextNode);
2354
- range.detach();
2355
- range2.detach();
2356
-
2357
- rangeProto.getName = function() {
2358
- return "WrappedRange";
2359
- };
2360
-
2361
- api.WrappedRange = WrappedRange;
2362
-
2363
- api.createNativeRange = function(doc) {
2364
- doc = getContentDocument(doc, module, "createNativeRange");
2365
- return doc.createRange();
2366
- };
2367
- })();
2368
- }
2369
-
2370
- if (api.features.implementsTextRange) {
2371
- /*
2372
- This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
2373
- method. For example, in the following (where pipes denote the selection boundaries):
2374
-
2375
- <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
2376
-
2377
- var range = document.selection.createRange();
2378
- alert(range.parentElement().id); // Should alert "ul" but alerts "b"
2379
-
2380
- This method returns the common ancestor node of the following:
2381
- - the parentElement() of the textRange
2382
- - the parentElement() of the textRange after calling collapse(true)
2383
- - the parentElement() of the textRange after calling collapse(false)
2384
- */
2385
- var getTextRangeContainerElement = function(textRange) {
2386
- var parentEl = textRange.parentElement();
2387
- var range = textRange.duplicate();
2388
- range.collapse(true);
2389
- var startEl = range.parentElement();
2390
- range = textRange.duplicate();
2391
- range.collapse(false);
2392
- var endEl = range.parentElement();
2393
- var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
2394
-
2395
- return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
2396
- };
2397
-
2398
- var textRangeIsCollapsed = function(textRange) {
2399
- return textRange.compareEndPoints("StartToEnd", textRange) == 0;
2400
- };
2401
-
2402
- // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
2403
- // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
2404
- // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
2405
- // for inputs and images, plus optimizations.
2406
- var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
2407
- var workingRange = textRange.duplicate();
2408
- workingRange.collapse(isStart);
2409
- var containerElement = workingRange.parentElement();
2410
-
2411
- // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
2412
- // check for that
2413
- if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
2414
- containerElement = wholeRangeContainerElement;
2415
- }
2416
-
2417
-
2418
- // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
2419
- // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
2420
- if (!containerElement.canHaveHTML) {
2421
- var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
2422
- return {
2423
- boundaryPosition: pos,
2424
- nodeInfo: {
2425
- nodeIndex: pos.offset,
2426
- containerElement: pos.node
2427
- }
2428
- };
2429
- }
2430
-
2431
- var workingNode = dom.getDocument(containerElement).createElement("span");
2432
-
2433
- // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
2434
- // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
2435
- if (workingNode.parentNode) {
2436
- workingNode.parentNode.removeChild(workingNode);
2437
- }
2438
-
2439
- var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
2440
- var previousNode, nextNode, boundaryPosition, boundaryNode;
2441
- var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
2442
- var childNodeCount = containerElement.childNodes.length;
2443
- var end = childNodeCount;
2444
-
2445
- // Check end first. Code within the loop assumes that the endth child node of the container is definitely
2446
- // after the range boundary.
2447
- var nodeIndex = end;
2448
-
2449
- while (true) {
2450
- if (nodeIndex == childNodeCount) {
2451
- containerElement.appendChild(workingNode);
2452
- } else {
2453
- containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
2454
- }
2455
- workingRange.moveToElementText(workingNode);
2456
- comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
2457
- if (comparison == 0 || start == end) {
2458
- break;
2459
- } else if (comparison == -1) {
2460
- if (end == start + 1) {
2461
- // We know the endth child node is after the range boundary, so we must be done.
2462
- break;
2463
- } else {
2464
- start = nodeIndex;
2465
- }
2466
- } else {
2467
- end = (end == start + 1) ? start : nodeIndex;
2468
- }
2469
- nodeIndex = Math.floor((start + end) / 2);
2470
- containerElement.removeChild(workingNode);
2471
- }
2472
-
2473
-
2474
- // We've now reached or gone past the boundary of the text range we're interested in
2475
- // so have identified the node we want
2476
- boundaryNode = workingNode.nextSibling;
2477
-
2478
- if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
2479
- // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
2480
- // node containing the text range's boundary, so we move the end of the working range to the boundary point
2481
- // and measure the length of its text to get the boundary's offset within the node.
2482
- workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
2483
-
2484
- var offset;
2485
-
2486
- if (/[\r\n]/.test(boundaryNode.data)) {
2487
- /*
2488
- For the particular case of a boundary within a text node containing rendered line breaks (within a <pre>
2489
- element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
2490
- facts:
2491
-
2492
- - Each line break is represented as \r in the text node's data/nodeValue properties
2493
- - Each line break is represented as \r\n in the TextRange's 'text' property
2494
- - The 'text' property of the TextRange does not contain trailing line breaks
2495
-
2496
- To get round the problem presented by the final fact above, we can use the fact that TextRange's
2497
- moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
2498
- the same as the number of characters it was instructed to move. The simplest approach is to use this to
2499
- store the characters moved when moving both the start and end of the range to the start of the document
2500
- body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
2501
- However, this is extremely slow when the document is large and the range is near the end of it. Clearly
2502
- doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
2503
- problem.
2504
-
2505
- Another approach that works is to use moveStart() to move the start boundary of the range up to the end
2506
- boundary one character at a time and incrementing a counter with the value returned by the moveStart()
2507
- call. However, the check for whether the start boundary has reached the end boundary is expensive, so
2508
- this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
2509
- the range within the document).
2510
-
2511
- The method below is a hybrid of the two methods above. It uses the fact that a string containing the
2512
- TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
2513
- text of the TextRange, so the start of the range is moved that length initially and then a character at
2514
- a time to make up for any trailing line breaks not contained in the 'text' property. This has good
2515
- performance in most situations compared to the previous two methods.
2516
- */
2517
- var tempRange = workingRange.duplicate();
2518
- var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
2519
-
2520
- offset = tempRange.moveStart("character", rangeLength);
2521
- while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
2522
- offset++;
2523
- tempRange.moveStart("character", 1);
2524
- }
2525
- } else {
2526
- offset = workingRange.text.length;
2527
- }
2528
- boundaryPosition = new DomPosition(boundaryNode, offset);
2529
- } else {
2530
-
2531
- // If the boundary immediately follows a character data node and this is the end boundary, we should favour
2532
- // a position within that, and likewise for a start boundary preceding a character data node
2533
- previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
2534
- nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
2535
- if (nextNode && isCharacterDataNode(nextNode)) {
2536
- boundaryPosition = new DomPosition(nextNode, 0);
2537
- } else if (previousNode && isCharacterDataNode(previousNode)) {
2538
- boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
2539
- } else {
2540
- boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
2541
- }
2542
- }
2543
-
2544
- // Clean up
2545
- workingNode.parentNode.removeChild(workingNode);
2546
-
2547
- return {
2548
- boundaryPosition: boundaryPosition,
2549
- nodeInfo: {
2550
- nodeIndex: nodeIndex,
2551
- containerElement: containerElement
2552
- }
2553
- };
2554
- };
2555
-
2556
- // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
2557
- // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
2558
- // (http://code.google.com/p/ierange/)
2559
- var createBoundaryTextRange = function(boundaryPosition, isStart) {
2560
- var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
2561
- var doc = dom.getDocument(boundaryPosition.node);
2562
- var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
2563
- var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
2564
-
2565
- if (nodeIsDataNode) {
2566
- boundaryNode = boundaryPosition.node;
2567
- boundaryParent = boundaryNode.parentNode;
2568
- } else {
2569
- childNodes = boundaryPosition.node.childNodes;
2570
- boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
2571
- boundaryParent = boundaryPosition.node;
2572
- }
2573
-
2574
- // Position the range immediately before the node containing the boundary
2575
- workingNode = doc.createElement("span");
2576
-
2577
- // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
2578
- // element rather than immediately before or after it
2579
- workingNode.innerHTML = "&#feff;";
2580
-
2581
- // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
2582
- // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
2583
- if (boundaryNode) {
2584
- boundaryParent.insertBefore(workingNode, boundaryNode);
2585
- } else {
2586
- boundaryParent.appendChild(workingNode);
2587
- }
2588
-
2589
- workingRange.moveToElementText(workingNode);
2590
- workingRange.collapse(!isStart);
2591
-
2592
- // Clean up
2593
- boundaryParent.removeChild(workingNode);
2594
-
2595
- // Move the working range to the text offset, if required
2596
- if (nodeIsDataNode) {
2597
- workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
2598
- }
2599
-
2600
- return workingRange;
2601
- };
2602
-
2603
- /*------------------------------------------------------------------------------------------------------------*/
2604
-
2605
- // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
2606
- // prototype
2607
-
2608
- WrappedTextRange = function(textRange) {
2609
- this.textRange = textRange;
2610
- this.refresh();
2611
- };
2612
-
2613
- WrappedTextRange.prototype = new DomRange(document);
2614
-
2615
- WrappedTextRange.prototype.refresh = function() {
2616
- var start, end, startBoundary;
2617
-
2618
- // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
2619
- var rangeContainerElement = getTextRangeContainerElement(this.textRange);
2620
-
2621
- if (textRangeIsCollapsed(this.textRange)) {
2622
- end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
2623
- true).boundaryPosition;
2624
- } else {
2625
- startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
2626
- start = startBoundary.boundaryPosition;
2627
-
2628
- // An optimization used here is that if the start and end boundaries have the same parent element, the
2629
- // search scope for the end boundary can be limited to exclude the portion of the element that precedes
2630
- // the start boundary
2631
- end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
2632
- startBoundary.nodeInfo).boundaryPosition;
2633
- }
2634
-
2635
- this.setStart(start.node, start.offset);
2636
- this.setEnd(end.node, end.offset);
2637
- };
2638
-
2639
- WrappedTextRange.prototype.getName = function() {
2640
- return "WrappedTextRange";
2641
- };
2642
-
2643
- DomRange.copyComparisonConstants(WrappedTextRange);
2644
-
2645
- WrappedTextRange.rangeToTextRange = function(range) {
2646
- if (range.collapsed) {
2647
- return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2648
- } else {
2649
- var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2650
- var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
2651
- var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
2652
- textRange.setEndPoint("StartToStart", startRange);
2653
- textRange.setEndPoint("EndToEnd", endRange);
2654
- return textRange;
2655
- }
2656
- };
2657
-
2658
- api.WrappedTextRange = WrappedTextRange;
2659
-
2660
- // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
2661
- // implementation to use by default.
2662
- if (!api.features.implementsDomRange || api.config.preferTextRange) {
2663
- // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
2664
- var globalObj = (function() { return this; })();
2665
- if (typeof globalObj.Range == "undefined") {
2666
- globalObj.Range = WrappedTextRange;
2667
- }
2668
-
2669
- api.createNativeRange = function(doc) {
2670
- doc = getContentDocument(doc, module, "createNativeRange");
2671
- return getBody(doc).createTextRange();
2672
- };
2673
-
2674
- api.WrappedRange = WrappedTextRange;
2675
- }
2676
- }
2677
-
2678
- api.createRange = function(doc) {
2679
- doc = getContentDocument(doc, module, "createRange");
2680
- return new api.WrappedRange(api.createNativeRange(doc));
2681
- };
2682
-
2683
- api.createRangyRange = function(doc) {
2684
- doc = getContentDocument(doc, module, "createRangyRange");
2685
- return new DomRange(doc);
2686
- };
2687
-
2688
- api.createIframeRange = function(iframeEl) {
2689
- module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
2690
- return api.createRange(iframeEl);
2691
- };
2692
-
2693
- api.createIframeRangyRange = function(iframeEl) {
2694
- module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
2695
- return api.createRangyRange(iframeEl);
2696
- };
2697
-
2698
- api.addCreateMissingNativeApiListener(function(win) {
2699
- var doc = win.document;
2700
- if (typeof doc.createRange == "undefined") {
2701
- doc.createRange = function() {
2702
- return api.createRange(doc);
2703
- };
2704
- }
2705
- doc = win = null;
2706
- });
2707
- });
2708
- // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
2709
- // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
2710
- rangy.createModule("WrappedSelection", function(api, module) {
2711
- api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
2712
-
2713
- api.config.checkSelectionRanges = true;
2714
-
2715
- var BOOLEAN = "boolean";
2716
- var dom = api.dom;
2717
- var util = api.util;
2718
- var isHostMethod = util.isHostMethod;
2719
- var DomRange = api.DomRange;
2720
- var WrappedRange = api.WrappedRange;
2721
- var DOMException = api.DOMException;
2722
- var DomPosition = dom.DomPosition;
2723
- var getNativeSelection;
2724
- var selectionIsCollapsed;
2725
- var features = api.features;
2726
- var CONTROL = "Control";
2727
- var getDocument = dom.getDocument;
2728
- var getBody = dom.getBody;
2729
- var rangesEqual = DomRange.rangesEqual;
2730
-
2731
-
2732
- // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
2733
- // Boolean (true for backwards).
2734
- function isDirectionBackward(dir) {
2735
- return (typeof dir == "string") ? (dir == "backward") : !!dir;
2736
- }
2737
-
2738
- function getWindow(win, methodName) {
2739
- if (!win) {
2740
- return window;
2741
- } else if (dom.isWindow(win)) {
2742
- return win;
2743
- } else if (win instanceof WrappedSelection) {
2744
- return win.win;
2745
- } else {
2746
- var doc = dom.getContentDocument(win, module, methodName);
2747
- return dom.getWindow(doc);
2748
- }
2749
- }
2750
-
2751
- function getWinSelection(winParam) {
2752
- return getWindow(winParam, "getWinSelection").getSelection();
2753
- }
2754
-
2755
- function getDocSelection(winParam) {
2756
- return getWindow(winParam, "getDocSelection").document.selection;
2757
- }
2758
-
2759
- // Test for the Range/TextRange and Selection features required
2760
- // Test for ability to retrieve selection
2761
- var implementsWinGetSelection = isHostMethod(window, "getSelection"),
2762
- implementsDocSelection = util.isHostObject(document, "selection");
2763
-
2764
- features.implementsWinGetSelection = implementsWinGetSelection;
2765
- features.implementsDocSelection = implementsDocSelection;
2766
-
2767
- var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
2768
-
2769
- if (useDocumentSelection) {
2770
- getNativeSelection = getDocSelection;
2771
- api.isSelectionValid = function(winParam) {
2772
- var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
2773
-
2774
- // Check whether the selection TextRange is actually contained within the correct document
2775
- return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
2776
- };
2777
- } else if (implementsWinGetSelection) {
2778
- getNativeSelection = getWinSelection;
2779
- api.isSelectionValid = function() {
2780
- return true;
2781
- };
2782
- } else {
2783
- module.fail("Neither document.selection or window.getSelection() detected.");
2784
- }
2785
-
2786
- api.getNativeSelection = getNativeSelection;
2787
-
2788
- var testSelection = getNativeSelection();
2789
- var testRange = api.createNativeRange(document);
2790
- var body = getBody(document);
2791
-
2792
- // Obtaining a range from a selection
2793
- var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
2794
- ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
2795
-
2796
- features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
2797
-
2798
- // Test for existence of native selection extend() method
2799
- var selectionHasExtend = isHostMethod(testSelection, "extend");
2800
- features.selectionHasExtend = selectionHasExtend;
2801
-
2802
- // Test if rangeCount exists
2803
- var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
2804
- features.selectionHasRangeCount = selectionHasRangeCount;
2805
-
2806
- var selectionSupportsMultipleRanges = false;
2807
- var collapsedNonEditableSelectionsSupported = true;
2808
-
2809
- if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
2810
- typeof testSelection.rangeCount == "number" && features.implementsDomRange) {
2811
-
2812
- (function() {
2813
- // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
2814
- // performed on the current document's selection. See issue 109.
2815
-
2816
- // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
2817
- // because initialization usually happens when the document loads, but could be a problem for a script that
2818
- // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
2819
- // selection.
2820
- var sel = window.getSelection();
2821
- if (sel) {
2822
- var body = getBody(document);
2823
- var testEl = body.appendChild( document.createElement("div") );
2824
- testEl.contentEditable = "false";
2825
- var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
2826
-
2827
- // Test whether the native selection will allow a collapsed selection within a non-editable element
2828
- var r1 = document.createRange();
2829
-
2830
- r1.setStart(textNode, 1);
2831
- r1.collapse(true);
2832
- sel.addRange(r1);
2833
- collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
2834
- sel.removeAllRanges();
2835
-
2836
- // Test whether the native selection is capable of supporting multiple ranges
2837
- var r2 = r1.cloneRange();
2838
- r1.setStart(textNode, 0);
2839
- r2.setEnd(textNode, 3);
2840
- r2.setStart(textNode, 2);
2841
- sel.addRange(r1);
2842
- sel.addRange(r2);
2843
-
2844
- selectionSupportsMultipleRanges = (sel.rangeCount == 2);
2845
-
2846
- // Clean up
2847
- body.removeChild(testEl);
2848
- sel.removeAllRanges();
2849
- r1.detach();
2850
- r2.detach();
2851
- }
2852
- })();
2853
- }
2854
-
2855
- features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
2856
- features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
2857
-
2858
- // ControlRanges
2859
- var implementsControlRange = false, testControlRange;
2860
-
2861
- if (body && isHostMethod(body, "createControlRange")) {
2862
- testControlRange = body.createControlRange();
2863
- if (util.areHostProperties(testControlRange, ["item", "add"])) {
2864
- implementsControlRange = true;
2865
- }
2866
- }
2867
- features.implementsControlRange = implementsControlRange;
2868
-
2869
- // Selection collapsedness
2870
- if (selectionHasAnchorAndFocus) {
2871
- selectionIsCollapsed = function(sel) {
2872
- return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
2873
- };
2874
- } else {
2875
- selectionIsCollapsed = function(sel) {
2876
- return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
2877
- };
2878
- }
2879
-
2880
- function updateAnchorAndFocusFromRange(sel, range, backward) {
2881
- var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
2882
- sel.anchorNode = range[anchorPrefix + "Container"];
2883
- sel.anchorOffset = range[anchorPrefix + "Offset"];
2884
- sel.focusNode = range[focusPrefix + "Container"];
2885
- sel.focusOffset = range[focusPrefix + "Offset"];
2886
- }
2887
-
2888
- function updateAnchorAndFocusFromNativeSelection(sel) {
2889
- var nativeSel = sel.nativeSelection;
2890
- sel.anchorNode = nativeSel.anchorNode;
2891
- sel.anchorOffset = nativeSel.anchorOffset;
2892
- sel.focusNode = nativeSel.focusNode;
2893
- sel.focusOffset = nativeSel.focusOffset;
2894
- }
2895
-
2896
- function updateEmptySelection(sel) {
2897
- sel.anchorNode = sel.focusNode = null;
2898
- sel.anchorOffset = sel.focusOffset = 0;
2899
- sel.rangeCount = 0;
2900
- sel.isCollapsed = true;
2901
- sel._ranges.length = 0;
2902
- }
2903
-
2904
- function getNativeRange(range) {
2905
- var nativeRange;
2906
- if (range instanceof DomRange) {
2907
- nativeRange = api.createNativeRange(range.getDocument());
2908
- nativeRange.setEnd(range.endContainer, range.endOffset);
2909
- nativeRange.setStart(range.startContainer, range.startOffset);
2910
- } else if (range instanceof WrappedRange) {
2911
- nativeRange = range.nativeRange;
2912
- } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
2913
- nativeRange = range;
2914
- }
2915
- return nativeRange;
2916
- }
2917
-
2918
- function rangeContainsSingleElement(rangeNodes) {
2919
- if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
2920
- return false;
2921
- }
2922
- for (var i = 1, len = rangeNodes.length; i < len; ++i) {
2923
- if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
2924
- return false;
2925
- }
2926
- }
2927
- return true;
2928
- }
2929
-
2930
- function getSingleElementFromRange(range) {
2931
- var nodes = range.getNodes();
2932
- if (!rangeContainsSingleElement(nodes)) {
2933
- throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
2934
- }
2935
- return nodes[0];
2936
- }
2937
-
2938
- // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
2939
- function isTextRange(range) {
2940
- return !!range && typeof range.text != "undefined";
2941
- }
2942
-
2943
- function updateFromTextRange(sel, range) {
2944
- // Create a Range from the selected TextRange
2945
- var wrappedRange = new WrappedRange(range);
2946
- sel._ranges = [wrappedRange];
2947
-
2948
- updateAnchorAndFocusFromRange(sel, wrappedRange, false);
2949
- sel.rangeCount = 1;
2950
- sel.isCollapsed = wrappedRange.collapsed;
2951
- }
2952
-
2953
- function updateControlSelection(sel) {
2954
- // Update the wrapped selection based on what's now in the native selection
2955
- sel._ranges.length = 0;
2956
- if (sel.docSelection.type == "None") {
2957
- updateEmptySelection(sel);
2958
- } else {
2959
- var controlRange = sel.docSelection.createRange();
2960
- if (isTextRange(controlRange)) {
2961
- // This case (where the selection type is "Control" and calling createRange() on the selection returns
2962
- // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
2963
- // ControlRange have been removed from the ControlRange and removed from the document.
2964
- updateFromTextRange(sel, controlRange);
2965
- } else {
2966
- sel.rangeCount = controlRange.length;
2967
- var range, doc = getDocument(controlRange.item(0));
2968
- for (var i = 0; i < sel.rangeCount; ++i) {
2969
- range = api.createRange(doc);
2970
- range.selectNode(controlRange.item(i));
2971
- sel._ranges.push(range);
2972
- }
2973
- sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
2974
- updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
2975
- }
2976
- }
2977
- }
2978
-
2979
- function addRangeToControlSelection(sel, range) {
2980
- var controlRange = sel.docSelection.createRange();
2981
- var rangeElement = getSingleElementFromRange(range);
2982
-
2983
- // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
2984
- // contained by the supplied range
2985
- var doc = getDocument(controlRange.item(0));
2986
- var newControlRange = getBody(doc).createControlRange();
2987
- for (var i = 0, len = controlRange.length; i < len; ++i) {
2988
- newControlRange.add(controlRange.item(i));
2989
- }
2990
- try {
2991
- newControlRange.add(rangeElement);
2992
- } catch (ex) {
2993
- throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
2994
- }
2995
- newControlRange.select();
2996
-
2997
- // Update the wrapped selection based on what's now in the native selection
2998
- updateControlSelection(sel);
2999
- }
3000
-
3001
- var getSelectionRangeAt;
3002
-
3003
- if (isHostMethod(testSelection, "getRangeAt")) {
3004
- // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
3005
- // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
3006
- // lesson to us all, especially me.
3007
- getSelectionRangeAt = function(sel, index) {
3008
- try {
3009
- return sel.getRangeAt(index);
3010
- } catch (ex) {
3011
- return null;
3012
- }
3013
- };
3014
- } else if (selectionHasAnchorAndFocus) {
3015
- getSelectionRangeAt = function(sel) {
3016
- var doc = getDocument(sel.anchorNode);
3017
- var range = api.createRange(doc);
3018
- range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
3019
-
3020
- // Handle the case when the selection was selected backwards (from the end to the start in the
3021
- // document)
3022
- if (range.collapsed !== this.isCollapsed) {
3023
- range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
3024
- }
3025
-
3026
- return range;
3027
- };
3028
- }
3029
-
3030
- function WrappedSelection(selection, docSelection, win) {
3031
- this.nativeSelection = selection;
3032
- this.docSelection = docSelection;
3033
- this._ranges = [];
3034
- this.win = win;
3035
- this.refresh();
3036
- }
3037
-
3038
- function deleteProperties(sel) {
3039
- sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
3040
- sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
3041
- sel.detached = true;
3042
- }
3043
-
3044
- var cachedRangySelections = [];
3045
-
3046
- function findCachedSelection(win, action) {
3047
- var i = cachedRangySelections.length, cached, sel;
3048
- while (i--) {
3049
- cached = cachedRangySelections[i];
3050
- sel = cached.selection;
3051
- if (action == "deleteAll") {
3052
- deleteProperties(sel);
3053
- } else if (cached.win == win) {
3054
- if (action == "delete") {
3055
- cachedRangySelections.splice(i, 1);
3056
- return true;
3057
- } else {
3058
- return sel;
3059
- }
3060
- }
3061
- }
3062
- if (action == "deleteAll") {
3063
- cachedRangySelections.length = 0;
3064
- }
3065
- return null;
3066
- }
3067
-
3068
- var getSelection = function(win) {
3069
- // Check if the parameter is a Rangy Selection object
3070
- if (win && win instanceof WrappedSelection) {
3071
- win.refresh();
3072
- return win;
3073
- }
3074
-
3075
- win = getWindow(win, "getNativeSelection");
3076
-
3077
- var sel = findCachedSelection(win);
3078
- var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
3079
- if (sel) {
3080
- sel.nativeSelection = nativeSel;
3081
- sel.docSelection = docSel;
3082
- sel.refresh();
3083
- } else {
3084
- sel = new WrappedSelection(nativeSel, docSel, win);
3085
- cachedRangySelections.push( { win: win, selection: sel } );
3086
- }
3087
- return sel;
3088
- };
3089
-
3090
- api.getSelection = getSelection;
3091
-
3092
- api.getIframeSelection = function(iframeEl) {
3093
- module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
3094
- return api.getSelection(dom.getIframeWindow(iframeEl));
3095
- };
3096
-
3097
- var selProto = WrappedSelection.prototype;
3098
-
3099
- function createControlSelection(sel, ranges) {
3100
- // Ensure that the selection becomes of type "Control"
3101
- var doc = getDocument(ranges[0].startContainer);
3102
- var controlRange = getBody(doc).createControlRange();
3103
- for (var i = 0, el; i < rangeCount; ++i) {
3104
- el = getSingleElementFromRange(ranges[i]);
3105
- try {
3106
- controlRange.add(el);
3107
- } catch (ex) {
3108
- throw module.createError("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
3109
- }
3110
- }
3111
- controlRange.select();
3112
-
3113
- // Update the wrapped selection based on what's now in the native selection
3114
- updateControlSelection(sel);
3115
- }
3116
-
3117
- // Selecting a range
3118
- if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
3119
- selProto.removeAllRanges = function() {
3120
- this.nativeSelection.removeAllRanges();
3121
- updateEmptySelection(this);
3122
- };
3123
-
3124
- var addRangeBackward = function(sel, range) {
3125
- var doc = DomRange.getRangeDocument(range);
3126
- var endRange = api.createRange(doc);
3127
- endRange.collapseToPoint(range.endContainer, range.endOffset);
3128
- sel.nativeSelection.addRange(getNativeRange(endRange));
3129
- sel.nativeSelection.extend(range.startContainer, range.startOffset);
3130
- sel.refresh();
3131
- };
3132
-
3133
- if (selectionHasRangeCount) {
3134
- selProto.addRange = function(range, direction) {
3135
- if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3136
- addRangeToControlSelection(this, range);
3137
- } else {
3138
- if (isDirectionBackward(direction) && selectionHasExtend) {
3139
- addRangeBackward(this, range);
3140
- } else {
3141
- var previousRangeCount;
3142
- if (selectionSupportsMultipleRanges) {
3143
- previousRangeCount = this.rangeCount;
3144
- } else {
3145
- this.removeAllRanges();
3146
- previousRangeCount = 0;
3147
- }
3148
- // Clone the native range so that changing the selected range does not affect the selection.
3149
- // This is contrary to the spec but is the only way to achieve consistency between browsers. See
3150
- // issue 80.
3151
- this.nativeSelection.addRange(getNativeRange(range).cloneRange());
3152
-
3153
- // Check whether adding the range was successful
3154
- this.rangeCount = this.nativeSelection.rangeCount;
3155
-
3156
- if (this.rangeCount == previousRangeCount + 1) {
3157
- // The range was added successfully
3158
-
3159
- // Check whether the range that we added to the selection is reflected in the last range extracted from
3160
- // the selection
3161
- if (api.config.checkSelectionRanges) {
3162
- var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
3163
- if (nativeRange && !rangesEqual(nativeRange, range)) {
3164
- // Happens in WebKit with, for example, a selection placed at the start of a text node
3165
- range = new WrappedRange(nativeRange);
3166
- }
3167
- }
3168
- this._ranges[this.rangeCount - 1] = range;
3169
- updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
3170
- this.isCollapsed = selectionIsCollapsed(this);
3171
- } else {
3172
- // The range was not added successfully. The simplest thing is to refresh
3173
- this.refresh();
3174
- }
3175
- }
3176
- }
3177
- };
3178
- } else {
3179
- selProto.addRange = function(range, direction) {
3180
- if (isDirectionBackward(direction) && selectionHasExtend) {
3181
- addRangeBackward(this, range);
3182
- } else {
3183
- this.nativeSelection.addRange(getNativeRange(range));
3184
- this.refresh();
3185
- }
3186
- };
3187
- }
3188
-
3189
- selProto.setRanges = function(ranges) {
3190
- if (implementsControlRange && ranges.length > 1) {
3191
- createControlSelection(this, ranges);
3192
- } else {
3193
- this.removeAllRanges();
3194
- for (var i = 0, len = ranges.length; i < len; ++i) {
3195
- this.addRange(ranges[i]);
3196
- }
3197
- }
3198
- };
3199
- } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
3200
- implementsControlRange && useDocumentSelection) {
3201
-
3202
- selProto.removeAllRanges = function() {
3203
- // Added try/catch as fix for issue #21
3204
- try {
3205
- this.docSelection.empty();
3206
-
3207
- // Check for empty() not working (issue #24)
3208
- if (this.docSelection.type != "None") {
3209
- // Work around failure to empty a control selection by instead selecting a TextRange and then
3210
- // calling empty()
3211
- var doc;
3212
- if (this.anchorNode) {
3213
- doc = getDocument(this.anchorNode);
3214
- } else if (this.docSelection.type == CONTROL) {
3215
- var controlRange = this.docSelection.createRange();
3216
- if (controlRange.length) {
3217
- doc = getDocument( controlRange.item(0) );
3218
- }
3219
- }
3220
- if (doc) {
3221
- var textRange = getBody(doc).createTextRange();
3222
- textRange.select();
3223
- this.docSelection.empty();
3224
- }
3225
- }
3226
- } catch(ex) {}
3227
- updateEmptySelection(this);
3228
- };
3229
-
3230
- selProto.addRange = function(range) {
3231
- if (this.docSelection.type == CONTROL) {
3232
- addRangeToControlSelection(this, range);
3233
- } else {
3234
- WrappedRange.rangeToTextRange(range).select();
3235
- this._ranges[0] = range;
3236
- this.rangeCount = 1;
3237
- this.isCollapsed = this._ranges[0].collapsed;
3238
- updateAnchorAndFocusFromRange(this, range, false);
3239
- }
3240
- };
3241
-
3242
- selProto.setRanges = function(ranges) {
3243
- this.removeAllRanges();
3244
- var rangeCount = ranges.length;
3245
- if (rangeCount > 1) {
3246
- createControlSelection(this, ranges);
3247
- } else if (rangeCount) {
3248
- this.addRange(ranges[0]);
3249
- }
3250
- };
3251
- } else {
3252
- module.fail("No means of selecting a Range or TextRange was found");
3253
- return false;
3254
- }
3255
-
3256
- selProto.getRangeAt = function(index) {
3257
- if (index < 0 || index >= this.rangeCount) {
3258
- throw new DOMException("INDEX_SIZE_ERR");
3259
- } else {
3260
- // Clone the range to preserve selection-range independence. See issue 80.
3261
- return this._ranges[index].cloneRange();
3262
- }
3263
- };
3264
-
3265
- var refreshSelection;
3266
-
3267
- if (useDocumentSelection) {
3268
- refreshSelection = function(sel) {
3269
- var range;
3270
- if (api.isSelectionValid(sel.win)) {
3271
- range = sel.docSelection.createRange();
3272
- } else {
3273
- range = getBody(sel.win.document).createTextRange();
3274
- range.collapse(true);
3275
- }
3276
-
3277
- if (sel.docSelection.type == CONTROL) {
3278
- updateControlSelection(sel);
3279
- } else if (isTextRange(range)) {
3280
- updateFromTextRange(sel, range);
3281
- } else {
3282
- updateEmptySelection(sel);
3283
- }
3284
- };
3285
- } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
3286
- refreshSelection = function(sel) {
3287
- if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
3288
- updateControlSelection(sel);
3289
- } else {
3290
- sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
3291
- if (sel.rangeCount) {
3292
- for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3293
- sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
3294
- }
3295
- updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
3296
- sel.isCollapsed = selectionIsCollapsed(sel);
3297
- } else {
3298
- updateEmptySelection(sel);
3299
- }
3300
- }
3301
- };
3302
- } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
3303
- refreshSelection = function(sel) {
3304
- var range, nativeSel = sel.nativeSelection;
3305
- if (nativeSel.anchorNode) {
3306
- range = getSelectionRangeAt(nativeSel, 0);
3307
- sel._ranges = [range];
3308
- sel.rangeCount = 1;
3309
- updateAnchorAndFocusFromNativeSelection(sel);
3310
- sel.isCollapsed = selectionIsCollapsed(sel);
3311
- } else {
3312
- updateEmptySelection(sel);
3313
- }
3314
- };
3315
- } else {
3316
- module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
3317
- return false;
3318
- }
3319
-
3320
- selProto.refresh = function(checkForChanges) {
3321
- var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
3322
- var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
3323
-
3324
- refreshSelection(this);
3325
- if (checkForChanges) {
3326
- // Check the range count first
3327
- var i = oldRanges.length;
3328
- if (i != this._ranges.length) {
3329
- return true;
3330
- }
3331
-
3332
- // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
3333
- // ranges after this
3334
- if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
3335
- return true;
3336
- }
3337
-
3338
- // Finally, compare each range in turn
3339
- while (i--) {
3340
- if (!rangesEqual(oldRanges[i], this._ranges[i])) {
3341
- return true;
3342
- }
3343
- }
3344
- return false;
3345
- }
3346
- };
3347
-
3348
- // Removal of a single range
3349
- var removeRangeManually = function(sel, range) {
3350
- var ranges = sel.getAllRanges();
3351
- sel.removeAllRanges();
3352
- for (var i = 0, len = ranges.length; i < len; ++i) {
3353
- if (!api.rangesEqual(range, ranges[i])) {
3354
- sel.addRange(ranges[i]);
3355
- }
3356
- }
3357
- if (!sel.rangeCount) {
3358
- updateEmptySelection(sel);
3359
- }
3360
- };
3361
-
3362
- if (implementsControlRange) {
3363
- selProto.removeRange = function(range) {
3364
- if (this.docSelection.type == CONTROL) {
3365
- var controlRange = this.docSelection.createRange();
3366
- var rangeElement = getSingleElementFromRange(range);
3367
-
3368
- // Create a new ControlRange containing all the elements in the selected ControlRange minus the
3369
- // element contained by the supplied range
3370
- var doc = getDocument(controlRange.item(0));
3371
- var newControlRange = getBody(doc).createControlRange();
3372
- var el, removed = false;
3373
- for (var i = 0, len = controlRange.length; i < len; ++i) {
3374
- el = controlRange.item(i);
3375
- if (el !== rangeElement || removed) {
3376
- newControlRange.add(controlRange.item(i));
3377
- } else {
3378
- removed = true;
3379
- }
3380
- }
3381
- newControlRange.select();
3382
-
3383
- // Update the wrapped selection based on what's now in the native selection
3384
- updateControlSelection(this);
3385
- } else {
3386
- removeRangeManually(this, range);
3387
- }
3388
- };
3389
- } else {
3390
- selProto.removeRange = function(range) {
3391
- removeRangeManually(this, range);
3392
- };
3393
- }
3394
-
3395
- // Detecting if a selection is backward
3396
- var selectionIsBackward;
3397
- if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
3398
- selectionIsBackward = function(sel) {
3399
- var backward = false;
3400
- if (sel.anchorNode) {
3401
- backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
3402
- }
3403
- return backward;
3404
- };
3405
-
3406
- selProto.isBackward = function() {
3407
- return selectionIsBackward(this);
3408
- };
3409
- } else {
3410
- selectionIsBackward = selProto.isBackward = function() {
3411
- return false;
3412
- };
3413
- }
3414
-
3415
- // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
3416
- selProto.isBackwards = selProto.isBackward;
3417
-
3418
- // Selection stringifier
3419
- // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
3420
- // The current spec does not yet define this method.
3421
- selProto.toString = function() {
3422
- var rangeTexts = [];
3423
- for (var i = 0, len = this.rangeCount; i < len; ++i) {
3424
- rangeTexts[i] = "" + this._ranges[i];
3425
- }
3426
- return rangeTexts.join("");
3427
- };
3428
-
3429
- function assertNodeInSameDocument(sel, node) {
3430
- if (sel.win.document != getDocument(node)) {
3431
- throw new DOMException("WRONG_DOCUMENT_ERR");
3432
- }
3433
- }
3434
-
3435
- // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
3436
- selProto.collapse = function(node, offset) {
3437
- assertNodeInSameDocument(this, node);
3438
- var range = api.createRange(node);
3439
- range.collapseToPoint(node, offset);
3440
- this.setSingleRange(range);
3441
- this.isCollapsed = true;
3442
- };
3443
-
3444
- selProto.collapseToStart = function() {
3445
- if (this.rangeCount) {
3446
- var range = this._ranges[0];
3447
- this.collapse(range.startContainer, range.startOffset);
3448
- } else {
3449
- throw new DOMException("INVALID_STATE_ERR");
3450
- }
3451
- };
3452
-
3453
- selProto.collapseToEnd = function() {
3454
- if (this.rangeCount) {
3455
- var range = this._ranges[this.rangeCount - 1];
3456
- this.collapse(range.endContainer, range.endOffset);
3457
- } else {
3458
- throw new DOMException("INVALID_STATE_ERR");
3459
- }
3460
- };
3461
-
3462
- // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
3463
- // never used by Rangy.
3464
- selProto.selectAllChildren = function(node) {
3465
- assertNodeInSameDocument(this, node);
3466
- var range = api.createRange(node);
3467
- range.selectNodeContents(node);
3468
- this.removeAllRanges();
3469
- this.addRange(range);
3470
- };
3471
-
3472
- selProto.deleteFromDocument = function() {
3473
- // Sepcial behaviour required for Control selections
3474
- if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3475
- var controlRange = this.docSelection.createRange();
3476
- var element;
3477
- while (controlRange.length) {
3478
- element = controlRange.item(0);
3479
- controlRange.remove(element);
3480
- element.parentNode.removeChild(element);
3481
- }
3482
- this.refresh();
3483
- } else if (this.rangeCount) {
3484
- var ranges = this.getAllRanges();
3485
- if (ranges.length) {
3486
- this.removeAllRanges();
3487
- for (var i = 0, len = ranges.length; i < len; ++i) {
3488
- ranges[i].deleteContents();
3489
- }
3490
- // The spec says nothing about what the selection should contain after calling deleteContents on each
3491
- // range. Firefox moves the selection to where the final selected range was, so we emulate that
3492
- this.addRange(ranges[len - 1]);
3493
- }
3494
- }
3495
- };
3496
-
3497
- // The following are non-standard extensions
3498
- selProto.eachRange = function(func, returnValue) {
3499
- for (var i = 0, len = this._ranges.length; i < len; ++i) {
3500
- if ( func( this.getRangeAt(i) ) ) {
3501
- return returnValue;
3502
- }
3503
- }
3504
- };
3505
-
3506
- selProto.getAllRanges = function() {
3507
- var ranges = [];
3508
- this.eachRange(function(range) {
3509
- ranges.push(range);
3510
- });
3511
- return ranges;
3512
- };
3513
-
3514
- selProto.setSingleRange = function(range, direction) {
3515
- this.removeAllRanges();
3516
- this.addRange(range, direction);
3517
- };
3518
-
3519
- selProto.callMethodOnEachRange = function(methodName, params) {
3520
- var results = [];
3521
- this.eachRange( function(range) {
3522
- results.push( range[methodName].apply(range, params) );
3523
- } );
3524
- return results;
3525
- };
3526
-
3527
- function createStartOrEndSetter(isStart) {
3528
- return function(node, offset) {
3529
- var range;
3530
- if (this.rangeCount) {
3531
- range = this.getRangeAt(0);
3532
- range["set" + (isStart ? "Start" : "End")](node, offset);
3533
- } else {
3534
- range = api.createRange(this.win.document);
3535
- range.setStartAndEnd(node, offset);
3536
- }
3537
- this.setSingleRange(range, this.isBackward());
3538
- };
3539
- }
3540
-
3541
- selProto.setStart = createStartOrEndSetter(true);
3542
- selProto.setEnd = createStartOrEndSetter(false);
3543
-
3544
- // Add cheeky select() method to Range prototype
3545
- api.rangePrototype.select = function(direction) {
3546
- getSelection( this.getDocument() ).setSingleRange(this, direction);
3547
- };
3548
-
3549
- selProto.changeEachRange = function(func) {
3550
- var ranges = [];
3551
- var backward = this.isBackward();
3552
-
3553
- this.eachRange(function(range) {
3554
- func(range);
3555
- ranges.push(range);
3556
- });
3557
-
3558
- this.removeAllRanges();
3559
- if (backward && ranges.length == 1) {
3560
- this.addRange(ranges[0], "backward");
3561
- } else {
3562
- this.setRanges(ranges);
3563
- }
3564
- };
3565
-
3566
- selProto.containsNode = function(node, allowPartial) {
3567
- return this.eachRange( function(range) {
3568
- return range.containsNode(node, allowPartial);
3569
- }, true );
3570
- };
3571
-
3572
- selProto.getBookmark = function(containerNode) {
3573
- return {
3574
- backward: this.isBackward(),
3575
- rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
3576
- };
3577
- };
3578
-
3579
- selProto.moveToBookmark = function(bookmark) {
3580
- var selRanges = [];
3581
- for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
3582
- range = api.createRange(this.win);
3583
- range.moveToBookmark(rangeBookmark);
3584
- selRanges.push(range);
3585
- }
3586
- if (bookmark.backward) {
3587
- this.setSingleRange(selRanges[0], "backward");
3588
- } else {
3589
- this.setRanges(selRanges);
3590
- }
3591
- };
3592
-
3593
- selProto.toHtml = function() {
3594
- return this.callMethodOnEachRange("toHtml").join("");
3595
- };
3596
-
3597
- function inspect(sel) {
3598
- var rangeInspects = [];
3599
- var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
3600
- var focus = new DomPosition(sel.focusNode, sel.focusOffset);
3601
- var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
3602
-
3603
- if (typeof sel.rangeCount != "undefined") {
3604
- for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3605
- rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
3606
- }
3607
- }
3608
- return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
3609
- ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
3610
- }
3611
-
3612
- selProto.getName = function() {
3613
- return "WrappedSelection";
3614
- };
3615
-
3616
- selProto.inspect = function() {
3617
- return inspect(this);
3618
- };
3619
-
3620
- selProto.detach = function() {
3621
- findCachedSelection(this.win, "delete");
3622
- deleteProperties(this);
3623
- };
3624
-
3625
- WrappedSelection.detachAll = function() {
3626
- findCachedSelection(null, "deleteAll");
3627
- };
3628
-
3629
- WrappedSelection.inspect = inspect;
3630
- WrappedSelection.isDirectionBackward = isDirectionBackward;
3631
-
3632
- api.Selection = WrappedSelection;
3633
-
3634
- api.selectionPrototype = selProto;
3635
-
3636
- api.addCreateMissingNativeApiListener(function(win) {
3637
- if (typeof win.getSelection == "undefined") {
3638
- win.getSelection = function() {
3639
- return getSelection(win);
3640
- };
3641
- }
3642
- win = null;
3643
- });
3644
- });
10
+ (function(a){function j(a,b){var e=typeof a[b];return e==d||e==c&&!!a[b]||e=="unknown"}function k(a,b){return typeof a[b]==c&&!!a[b]}function l(a,b){return typeof a[b]!=e}function m(a){return function(b,c){var d=c.length;while(d--)if(!a(b,c[d]))return!1;return!0}}function q(a){return a&&n(a,i)&&p(a,h)}function r(a){return k(a,"body")?a.body:a.getElementsByTagName("body")[0]}function u(a){k(window,"console")&&j(window.console,"log")&&window.console.log(a)}function v(a,b){b?window.alert(a):u(a)}function w(a){t.initialized=!0,t.supported=!1,v("Rangy is not supported on this page in your browser. Reason: "+a,t.config.alertOnFail)}function x(a){v("Rangy warning: "+a,t.config.alertOnWarn)}function A(a){return a.message||a.description||String(a)}function B(){if(t.initialized)return;var a,b=!1,c=!1;j(document,"createRange")&&(a=document.createRange(),n(a,g)&&p(a,f)&&(b=!0),a.detach());var d=r(document);if(!d||d.nodeName.toLowerCase()!="body"){w("No body element found");return}d&&j(d,"createTextRange")&&(a=d.createTextRange(),q(a)&&(c=!0));if(!b&&!c){w("Neither Range nor TextRange are available");return}t.initialized=!0,t.features={implementsDomRange:b,implementsTextRange:c};var e,h;for(var i in s)(e=s[i])instanceof E&&e.init(e,t);for(var k=0,l=z.length;k<l;++k)try{z[k](t)}catch(m){h="Rangy init listener threw an exception. Continuing. Detail: "+A(m),u(h)}}function D(a){a=a||window,B();for(var b=0,c=C.length;b<c;++b)C[b](a)}function E(a,b,c){this.name=a,this.dependencies=b,this.initialized=!1,this.supported=!1,this.initializer=c}function F(a,b,c,d){var e=new E(b,c,function(a){if(!a.initialized){a.initialized=!0;try{d(t,a),a.supported=!0}catch(c){var e="Module '"+b+"' failed to load: "+A(c);u(e)}}});s[b]=e}function G(){}function H(){}var b=typeof a.define=="function"&&a.define.amd,c="object",d="function",e="undefined",f=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],g=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],h=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],i=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],n=m(j),o=m(k),p=m(l),s={},t={version:"1.3alpha.780M",initialized:!1,supported:!0,util:{isHostMethod:j,isHostObject:k,isHostProperty:l,areHostMethods:n,areHostObjects:o,areHostProperties:p,isTextRange:q,getBody:r},features:{},modules:s,config:{alertOnFail:!0,alertOnWarn:!1,preferTextRange:!1}};t.fail=w,t.warn=x,{}.hasOwnProperty?t.util.extend=function(a,b,c){var d,e;for(var f in b)b.hasOwnProperty(f)&&(d=a[f],e=b[f],c&&d!==null&&typeof d=="object"&&e!==null&&typeof e=="object"&&t.util.extend(d,e,!0),a[f]=e);return a}:w("hasOwnProperty not supported"),function(){var a=document.createElement("div");a.appendChild(document.createElement("span"));var b=[].slice,c;try{b.call(a.childNodes,0)[0].nodeType==1&&(c=function(a){return b.call(a,0)})}catch(d){}c||(c=function(a){var b=[];for(var c=0,d=a.length;c<d;++c)b[c]=a[c];return b}),t.util.toArray=c}();var y;j(document,"addEventListener")?y=function(a,b,c){a.addEventListener(b,c,!1)}:j(document,"attachEvent")?y=function(a,b,c){a.attachEvent("on"+b,c)}:w("Document does not have required addEventListener or attachEvent method"),t.util.addListener=y;var z=[];t.init=B,t.addInitListener=function(a){t.initialized?a(t):z.push(a)};var C=[];t.addCreateMissingNativeApiListener=function(a){C.push(a)},t.createMissingNativeApi=D,E.prototype={init:function(a){var b=this.dependencies||[];for(var c=0,d=b.length,e,f;c<d;++c){f=b[c],e=s[f];if(!e||!(e instanceof E))throw new Error("required module '"+f+"' not found");e.init();if(!e.supported)throw new Error("required module '"+f+"' not supported")}this.initializer(this)},fail:function(a){throw this.initialized=!0,this.supported=!1,new Error("Module '"+this.name+"' failed to load: "+a)},warn:function(a){t.warn("Module "+this.name+": "+a)},deprecationNotice:function(a,b){t.warn("DEPRECATED: "+a+" in module "+this.name+"is deprecated. Please use "+b+" instead")},createError:function(a){return new Error("Error in Rangy "+this.name+" module: "+a)}},t.createModule=function(a){var b,c;arguments.length==2?(b=arguments[1],c=[]):(b=arguments[2],c=arguments[1]),F(!1,a,c,b)},t.createCoreModule=function(a,b,c){F(!0,a,b,c)},t.RangePrototype=G,t.rangePrototype=new G,t.selectionPrototype=new H;var I=!1,J=function(a){I||(I=!0,t.initialized||B())};if(typeof window==e){w("No window found");return}if(typeof document==e){w("No document found");return}j(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",J,!1),y(window,"load",J),b&&a.define(function(){return t.amd=!0,t}),a.rangy=t})(this),rangy.createCoreModule("DomUtil",[],function(a,b){function h(a){var b;return typeof a.namespaceURI==c||(b=a.namespaceURI)===null||b=="http://www.w3.org/1999/xhtml"}function i(a){var b=a.parentNode;return b.nodeType==1?b:null}function j(a){var b=0;while(a=a.previousSibling)++b;return b}function k(a){switch(a.nodeType){case 7:case 10:return 0;case 3:case 8:return a.length;default:return a.childNodes.length}}function l(a,b){var c=[],d;for(d=a;d;d=d.parentNode)c.push(d);for(d=b;d;d=d.parentNode)if(g(c,d))return d;return null}function m(a,b,c){var d=c?b:b.parentNode;while(d){if(d===a)return!0;d=d.parentNode}return!1}function n(a,b){return m(a,b,!0)}function o(a,b,c){var d,e=c?a:a.parentNode;while(e){d=e.parentNode;if(d===b)return e;e=d}return null}function p(a){var b=a.nodeType;return b==3||b==4||b==8}function q(a){if(!a)return!1;var b=a.nodeType;return b==3||b==8}function r(a,b){var c=b.nextSibling,d=b.parentNode;return c?d.insertBefore(a,c):d.appendChild(a),a}function s(a,b,c){var d=a.cloneNode(!1);d.deleteData(0,b),a.deleteData(b,a.length-b),r(d,a);if(c)for(var e=0,f;f=c[e++];)f.node==a&&f.offset>b?(f.node=d,f.offset-=b):f.node==a.parentNode&&f.offset>j(a)&&++f.offset;return d}function t(a){if(a.nodeType==9)return a;if(typeof a.ownerDocument!=c)return a.ownerDocument;if(typeof a.document!=c)return a.document;if(a.parentNode)return t(a.parentNode);throw b.createError("getDocument: no document found for node")}function u(a){var d=t(a);if(typeof d.defaultView!=c)return d.defaultView;if(typeof d.parentWindow!=c)return d.parentWindow;throw b.createError("Cannot get a window object for node")}function v(a){if(typeof a.contentDocument!=c)return a.contentDocument;if(typeof a.contentWindow!=c)return a.contentWindow.document;throw b.createError("getIframeDocument: No Document object found for iframe element")}function w(a){if(typeof a.contentWindow!=c)return a.contentWindow;if(typeof a.contentDocument!=c)return a.contentDocument.defaultView;throw b.createError("getIframeWindow: No Window object found for iframe element")}function x(a){return a&&d.isHostMethod(a,"setTimeout")&&d.isHostObject(a,"document")}function y(a,b,c){var e;a?d.isHostProperty(a,"nodeType")?e=a.nodeType==1&&a.tagName.toLowerCase()=="iframe"?v(a):t(a):x(a)&&(e=a.document):e=document;if(!e)throw b.createError(c+"(): Parameter must be a Window object or DOM node");return e}function z(a){var b;while(b=a.parentNode)a=b;return a}function A(a,c,d,e){var f,g,h,i,k;if(a==d)return c===e?0:c<e?-1:1;if(f=o(d,a,!0))return c<=j(f)?-1:1;if(f=o(a,d,!0))return j(f)<e?-1:1;g=l(a,d),h=a===g?g:o(a,g,!0),i=d===g?g:o(d,g,!0);if(h===i)throw b.createError("comparePoints got to case 4 and childA and childB are the same!");k=g.firstChild;while(k){if(k===h)return-1;if(k===i)return 1;k=k.nextSibling}}function C(a){try{return a.parentNode,!1}catch(b){return!0}}function D(a){if(!a)return"[No node]";if(B&&C(a))return"[Broken node]";if(p(a))return'"'+a.data+'"';if(a.nodeType==1){var b=a.id?' id="'+a.id+'"':"";return"<"+a.nodeName+b+">["+a.childNodes.length+"]["+a.innerHTML.slice(0,20)+"]"}return a.nodeName}function E(a){var b=t(a).createDocumentFragment(),c;while(c=a.firstChild)b.appendChild(c);return b}function G(a){this.root=a,this._next=a}function H(a){return new G(a)}function I(a,b){this.node=a,this.offset=b}function J(a){this.code=this[a],this.codeName=a,this.message="DOMException: "+this.codeName}var c="undefined",d=a.util;d.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||b.fail("document missing a Node creation method"),d.isHostMethod(document,"getElementsByTagName")||b.fail("document missing getElementsByTagName method");var e=document.createElement("div");d.areHostMethods(e,["insertBefore","appendChild","cloneNode"]||!d.areHostObjects(e,["previousSibling","nextSibling","childNodes","parentNode"]))||b.fail("Incomplete Element implementation"),d.isHostProperty(e,"innerHTML")||b.fail("Element is missing innerHTML property");var f=document.createTextNode("test");d.areHostMethods(f,["splitText","deleteData","insertData","appendData","cloneNode"]||!d.areHostObjects(e,["previousSibling","nextSibling","childNodes","parentNode"])||!d.areHostProperties(f,["data"]))||b.fail("Incomplete Text Node implementation");var g=function(a,b){var c=a.length;while(c--)if(a[c]===b)return!0;return!1},B=!1;(function(){var b=document.createElement("b");b.innerHTML="1";var c=b.firstChild;b.innerHTML="<br>",B=C(c),a.features.crashyTextNodes=B})();var F;typeof window.getComputedStyle!=c?F=function(a,b){return u(a).getComputedStyle(a,null)[b]}:typeof document.documentElement.currentStyle!=c?F=function(a,b){return a.currentStyle[b]}:b.fail("No means of obtaining computed style properties found"),G.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next,b,c;if(this._current){b=a.firstChild;if(b)this._next=b;else{c=null;while(a!==this.root&&!(c=a.nextSibling))a=a.parentNode;this._next=c}}return this._current},detach:function(){this._current=this._next=this.root=null}},I.prototype={equals:function(a){return!!a&&this.node===a.node&&this.offset==a.offset},inspect:function(){return"[DomPosition("+D(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},J.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11},J.prototype.toString=function(){return this.message},a.dom={arrayContains:g,isHtmlNamespace:h,parentElement:i,getNodeIndex:j,getNodeLength:k,getCommonAncestor:l,isAncestorOf:m,isOrIsAncestorOf:n,getClosestAncestorIn:o,isCharacterDataNode:p,isTextOrCommentNode:q,insertAfter:r,splitDataNode:s,getDocument:t,getWindow:u,getIframeWindow:w,getIframeDocument:v,getBody:d.getBody,isWindow:x,getContentDocument:y,getRootContainer:z,comparePoints:A,isBrokenNode:C,inspectNode:D,getComputedStyleProperty:F,fragmentFromNodeChildren:E,createIterator:H,DomPosition:I},a.DOMException=J}),rangy.createCoreModule("DomRange",["DomUtil"],function(a,b){function r(a,b){return a.nodeType!=3&&(i(a,b.startContainer)||i(a,b.endContainer))}function s(a){return a.document||j(a.startContainer)}function t(a){return new e(a.parentNode,h(a))}function u(a){return new e(a.parentNode,h(a)+1)}function v(a,b,d){var e=a.nodeType==11?a.firstChild:a;return g(b)?d==b.length?c.insertAfter(a,b):b.parentNode.insertBefore(a,d==0?b:l(b,d)):d>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[d]),e}function w(a,b,c){Y(a),Y(b);if(s(b)!=s(a))throw new f("WRONG_DOCUMENT_ERR");var d=k(a.startContainer,a.startOffset,b.endContainer,b.endOffset),e=k(a.endContainer,a.endOffset,b.startContainer,b.startOffset);return c?d<=0&&e>=0:d<0&&e>0}function x(a){var b;for(var c,d=s(a.range).createDocumentFragment(),e;c=a.next();){b=a.isPartiallySelectedSubtree(),c=c.cloneNode(!b),b&&(e=a.getSubtreeIterator(),c.appendChild(x(e)),e.detach(!0));if(c.nodeType==10)throw new f("HIERARCHY_REQUEST_ERR");d.appendChild(c)}return d}function y(a,b,d){var e,f;d=d||{stop:!1};for(var g,h;g=a.next();)if(a.isPartiallySelectedSubtree()){if(b(g)===!1){d.stop=!0;return}h=a.getSubtreeIterator(),y(h,b,d),h.detach(!0);if(d.stop)return}else{e=c.createIterator(g);while(f=e.next())if(b(f)===!1){d.stop=!0;return}}}function z(a){var b;while(a.next())a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),z(b),b.detach(!0)):a.remove()}function A(a){for(var b,c=s(a.range).createDocumentFragment(),d;b=a.next();){a.isPartiallySelectedSubtree()?(b=b.cloneNode(!1),d=a.getSubtreeIterator(),b.appendChild(A(d)),d.detach(!0)):a.remove();if(b.nodeType==10)throw new f("HIERARCHY_REQUEST_ERR");c.appendChild(b)}return c}function B(a,b,c){var d=!!b&&!!b.length,e,f=!!c;d&&(e=new RegExp("^("+b.join("|")+")$"));var g=[];return y(new D(a,!1),function(a){(!d||e.test(a.nodeType))&&(!f||c(a))&&g.push(a)}),g}function C(a){var b=typeof a.getName=="undefined"?"Range":a.getName();return"["+b+"("+c.inspectNode(a.startContainer)+":"+a.startOffset+", "+c.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function D(a,b){this.range=a,this.clonePartiallySelectedTextNodes=b;if(!a.collapsed){this.sc=a.startContainer,this.so=a.startOffset,this.ec=a.endContainer,this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&g(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc===c&&!g(this.sc)?this.sc.childNodes[this.so]:m(this.sc,c,!0),this._last=this.ec===c&&!g(this.ec)?this.ec.childNodes[this.eo-1]:m(this.ec,c,!0))}}function E(a){this.code=this[a],this.codeName=a,this.message="RangeException: "+this.codeName}function K(a){return function(b,c){var d,e=c?b:b.parentNode;while(e){d=e.nodeType;if(o(a,d))return e;e=e.parentNode}return null}}function O(a,b){if(N(a,b))throw new E("INVALID_NODE_TYPE_ERR")}function P(a){if(!a.startContainer)throw new f("INVALID_STATE_ERR")}function Q(a,b){if(!o(b,a.nodeType))throw new E("INVALID_NODE_TYPE_ERR")}function R(a,b){if(b<0||b>(g(a)?a.length:a.childNodes.length))throw new f("INDEX_SIZE_ERR")}function S(a,b){if(L(a,!0)!==L(b,!0))throw new f("WRONG_DOCUMENT_ERR")}function T(a){if(M(a,!0))throw new f("NO_MODIFICATION_ALLOWED_ERR")}function U(a,b){if(!a)throw new f(b)}function V(a){return q&&c.isBrokenNode(a)||!o(G,a.nodeType)&&!L(a,!0)}function W(a,b){return b<=(g(a)?a.length:a.childNodes.length)}function X(a){return!!a.startContainer&&!!a.endContainer&&!V(a.startContainer)&&!V(a.endContainer)&&W(a.startContainer,a.startOffset)&&W(a.endContainer,a.endOffset)}function Y(a){P(a);if(!X(a))throw new Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")")}function bb(a,b){Y(a);var c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset,i=c===e;g(e)&&f>0&&f<e.length&&l(e,f,b),g(c)&&d>0&&d<c.length&&(c=l(c,d,b),i?(f-=d,e=c):e==c.parentNode&&f>=h(c)&&f++,d=0),a.setStartAndEnd(c,d,e,f)}function lb(a){a.START_TO_START=db,a.START_TO_END=eb,a.END_TO_END=fb,a.END_TO_START=gb,a.NODE_BEFORE=hb,a.NODE_AFTER=ib,a.NODE_BEFORE_AND_AFTER=jb,a.NODE_INSIDE=kb}function mb(a){lb(a),lb(a.prototype)}function nb(a,b){return function(){Y(this);var c=this.startContainer,d=this.startOffset,e=this.commonAncestorContainer,f=new D(this,!0),g,h;c!==e&&(g=m(c,e,!0),h=u(g),c=h.node,d=h.offset),y(f,T),f.reset();var i=a(f);return f.detach(),b(this,c,d,c,d),i}}function ob(b,c,e){function f(a,b){return function(c){P(this),Q(c,F),Q(p(c),G);var d=(a?t:u)(c);(b?i:j)(this,d.node,d.offset)}}function i(a,b,d){var e=a.endContainer,f=a.endOffset;if(b!==a.startContainer||d!==a.startOffset){if(p(b)!=p(e)||k(b,d,e,f)==1)e=b,f=d;c(a,b,d,e,f)}}function j(a,b,d){var e=a.startContainer,f=a.startOffset;if(b!==a.endContainer||d!==a.endOffset){if(p(b)!=p(e)||k(b,d,e,f)==-1)e=b,f=d;c(a,e,f,b,d)}}var l=function(){};l.prototype=a.rangePrototype,b.prototype=new l,d.extend(b.prototype,{setStart:function(a,b){P(this),O(a,!0),R(a,b),i(this,a,b)},setEnd:function(a,b){P(this),O(a,!0),R(a,b),j(this,a,b)},setStartAndEnd:function(){P(this);var a=arguments,b=a[0],d=a[1],e=b,f=d;switch(a.length){case 3:f=a[2];break;case 4:e=a[2],f=a[3]}c(this,b,d,e,f)},setBoundary:function(a,b,c){this["set"+(c?"Start":"End")](a,b)},setStartBefore:f(!0,!0),setStartAfter:f(!1,!0),setEndBefore:f(!0,!1),setEndAfter:f(!1,!1),collapse:function(a){Y(this),a?c(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):c(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){P(this),O(a,!0),c(this,a,0,a,n(a))},selectNode:function(a){P(this),O(a,!1),Q(a,F);var b=t(a),d=u(a);c(this,b.node,b.offset,d.node,d.offset)},extractContents:nb(A,c),deleteContents:nb(z,c),canSurroundContents:function(){Y(this),T(this.startContainer),T(this.endContainer);var a=new D(this,!0),b=a._first&&r(a._first,this)||a._last&&r(a._last,this);return a.detach(),!b},detach:function(){e(this)},splitBoundaries:function(){bb(this)},splitBoundariesPreservingPositions:function(a){bb(this,a)},normalizeBoundaries:function(){Y(this);var a=this.startContainer,b=this.startOffset,d=this.endContainer,e=this.endOffset,f=function(a){var b=a.nextSibling;b&&b.nodeType==a.nodeType&&(d=a,e=a.length,a.appendData(b.data),b.parentNode.removeChild(b))},i=function(c){var f=c.previousSibling;if(f&&f.nodeType==c.nodeType){a=c;var g=c.length;b=f.length,c.insertData(0,f.data),f.parentNode.removeChild(f);if(a==d)e+=b,d=a;else if(d==c.parentNode){var i=h(c);e==i?(d=c,e=g):e>i&&e--}}},j=!0;if(g(d))d.length==e&&f(d);else{if(e>0){var k=d.childNodes[e-1];k&&g(k)&&f(k)}j=!this.collapsed}if(j){if(g(a))b==0&&i(a);else if(b<a.childNodes.length){var l=a.childNodes[b];l&&g(l)&&i(l)}}else a=d,b=e;c(this,a,b,d,e)},collapseToPoint:function(a,b){P(this),O(a,!0),R(a,b),this.setStartAndEnd(a,b)}}),mb(b)}function pb(a){a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset,a.commonAncestorContainer=a.collapsed?a.startContainer:c.getCommonAncestor(a.startContainer,a.endContainer)}function qb(a,b,d,e,f){a.startContainer=b,a.startOffset=d,a.endContainer=e,a.endOffset=f,a.document=c.getDocument(b),pb(a)}function rb(a){P(a),a.startContainer=a.startOffset=a.endContainer=a.endOffset=a.document=null,a.collapsed=a.commonAncestorContainer=null}function sb(a){this.startContainer=a,this.startOffset=0,this.endContainer=a,this.endOffset=0,this.document=a,pb(this)}var c=a.dom,d=a.util,e=c.DomPosition,f=a.DOMException,g=c.isCharacterDataNode,h=c.getNodeIndex,i=c.isOrIsAncestorOf,j=c.getDocument,k=c.comparePoints,l=c.splitDataNode,m=c.getClosestAncestorIn,n=c.getNodeLength,o=c.arrayContains,p=c.getRootContainer,q=a.features.crashyTextNodes;D.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:!1,reset:function(){this._current=null,this._next=this._first},hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next;return a&&(this._next=a!==this._last?a.nextSibling:null,g(a)&&this.clonePartiallySelectedTextNodes&&(a===this.ec&&(a=a.cloneNode(!0)).deleteData(this.eo,a.length-this.eo),this._current===this.sc&&(a=a.cloneNode(!0)).deleteData(0,this.so))),a},remove:function(){var a=this._current,b,c;!g(a)||a!==this.sc&&a!==this.ec?a.parentNode&&a.parentNode.removeChild(a):(b=a===this.sc?this.so:0,c=a===this.ec?this.eo:a.length,b!=c&&a.deleteData(b,c-b))},isPartiallySelectedSubtree:function(){var a=this._current;return r(a,this.range)},getSubtreeIterator:function(){var a;if(this.isSingleCharacterDataNode)a=this.range.cloneRange(),a.collapse(!1);else{a=new sb(s(this.range));var b=this._current,c=b,d=0,e=b,f=n(b);i(b,this.sc)&&(c=this.sc,d=this.so),i(b,this.ec)&&(e=this.ec,f=this.eo),qb(a,c,d,e,f)}return new D(a,this.clonePartiallySelectedTextNodes)},detach:function(a){a&&this.range.detach(),this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}},E.prototype={BAD_BOUNDARYPOINTS_ERR:1,INVALID_NODE_TYPE_ERR:2},E.prototype.toString=function(){return this.message};var F=[1,3,4,5,7,8,10],G=[2,9,11],H=[5,6,10,12],I=[1,3,4,5,7,8,10,11],J=[1,3,4,5,7,8],L=K([9,11]),M=K(H),N=K([6,10,12]),Z=document.createElement("style"),$=!1;try{Z.innerHTML="<b>x</b>",$=Z.firstChild.nodeType==3}catch(_){}a.features.htmlParsingConforms=$;var ab=$?function(a){var b=this.startContainer,d=j(b);if(!b)throw new f("INVALID_STATE_ERR");var e=null;return b.nodeType==1?e=b:g(b)&&(e=c.parentElement(b)),e===null||e.nodeName=="HTML"&&c.isHtmlNamespace(j(e).documentElement)&&c.isHtmlNamespace(e)?e=d.createElement("body"):e=e.cloneNode(!1),e.innerHTML=a,c.fragmentFromNodeChildren(e)}:function(a){P(this);var b=s(this),d=b.createElement("body");return d.innerHTML=a,c.fragmentFromNodeChildren(d)},cb=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],db=0,eb=1,fb=2,gb=3,hb=0,ib=1,jb=2,kb=3;d.extend(a.rangePrototype,{compareBoundaryPoints:function(a,b){Y(this),S(this.startContainer,b.startContainer);var c,d,e,f,g=a==gb||a==db?"start":"end",h=a==eb||a==db?"start":"end";return c=this[g+"Container"],d=this[g+"Offset"],e=b[h+"Container"],f=b[h+"Offset"],k(c,d,e,f)},insertNode:function(a){Y(this),Q(a,I),T(this.startContainer);if(i(a,this.startContainer))throw new f("HIERARCHY_REQUEST_ERR");var b=v(a,this.startContainer,this.startOffset);this.setStartBefore(b)},cloneContents:function(){Y(this);var a,b;if(this.collapsed)return s(this).createDocumentFragment();if(this.startContainer===this.endContainer&&g(this.startContainer))return a=this.startContainer.cloneNode(!0),a.data=a.data.slice(this.startOffset,this.endOffset),b=s(this).createDocumentFragment(),b.appendChild(a),b;var c=new D(this,!0);return a=x(c),c.detach(),a},canSurroundContents:function(){Y(this),T(this.startContainer),T(this.endContainer);var a=new D(this,!0),b=a._first&&r(a._first,this)||a._last&&r(a._last,this);return a.detach(),!b},surroundContents:function(a){Q(a,J);if(!this.canSurroundContents())throw new E("BAD_BOUNDARYPOINTS_ERR");var b=this.extractContents();if(a.hasChildNodes())while(a.lastChild)a.removeChild(a.lastChild);v(a,this.startContainer,this.startOffset),a.appendChild(b),this.selectNode(a)},cloneRange:function(){Y(this);var a=new sb(s(this)),b=cb.length,c;while(b--)c=cb[b],a[c]=this[c];return a},toString:function(){Y(this);var a=this.startContainer;if(a===this.endContainer&&g(a))return a.nodeType==3||a.nodeType==4?a.data.slice(this.startOffset,this.endOffset):"";var b=[],c=new D(this,!0);return y(c,function(a){(a.nodeType==3||a.nodeType==4)&&b.push(a.data)}),c.detach(),b.join("")},compareNode:function(a){Y(this);var b=a.parentNode,c=h(a);if(!b)throw new f("NOT_FOUND_ERR");var d=this.comparePoint(b,c),e=this.comparePoint(b,c+1);return d<0?e>0?jb:hb:e>0?ib:kb},comparePoint:function(a,b){return Y(this),U(a,"HIERARCHY_REQUEST_ERR"),S(a,this.startContainer),k(a,b,this.startContainer,this.startOffset)<0?-1:k(a,b,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:ab,toHtml:function(){Y(this);var a=this.commonAncestorContainer.parentNode.cloneNode(!1);return a.appendChild(this.cloneContents()),a.innerHTML},intersectsNode:function(a,b){Y(this),U(a,"NOT_FOUND_ERR");if(j(a)!==s(this))return!1;var c=a.parentNode,d=h(a);U(c,"NOT_FOUND_ERR");var e=k(c,d,this.endContainer,this.endOffset),f=k(c,d+1,this.startContainer,this.startOffset);return b?e<=0&&f>=0:e<0&&f>0},isPointInRange:function(a,b){return Y(this),U(a,"HIERARCHY_REQUEST_ERR"),S(a,this.startContainer),k(a,b,this.startContainer,this.startOffset)>=0&&k(a,b,this.endContainer,this.endOffset)<=0},intersectsRange:function(a){return w(this,a,!1)},intersectsOrTouchesRange:function(a){return w(this,a,!0)},intersection:function(a){if(this.intersectsRange(a)){var b=k(this.startContainer,this.startOffset,a.startContainer,a.startOffset),c=k(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange();return b==-1&&d.setStart(a.startContainer,a.startOffset),c==1&&d.setEnd(a.endContainer,a.endOffset),d}return null},union:function(a){if(this.intersectsOrTouchesRange(a)){var b=this.cloneRange();return k(a.startContainer,a.startOffset,this.startContainer,this.startOffset)==-1&&b.setStart(a.startContainer,a.startOffset),k(a.endContainer,a.endOffset,this.endContainer,this.endOffset)==1&&b.setEnd(a.endContainer,a.endOffset),b}throw new E("Ranges do not intersect")},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==kb},containsNodeContents:function(a){return this.comparePoint(a,0)>=0&&this.comparePoint(a,n(a))<=0},containsRange:function(a){var b=this.intersection(a);return b!==null&&a.equals(b)},containsNodeText:function(a){var b=this.cloneRange();b.selectNode(a);var c=b.getNodes([3]);if(c.length>0){b.setStart(c[0],0);var d=c.pop();b.setEnd(d,d.length);var e=this.containsRange(b);return b.detach(),e}return this.containsNodeContents(a)},getNodes:function(a,b){return Y(this),B(this,a,b)},getDocument:function(){return s(this)},collapseBefore:function(a){P(this),this.setEndBefore(a),this.collapse(!1)},collapseAfter:function(a){P(this),this.setStartAfter(a),this.collapse(!0)},getBookmark:function(b){var d=s(this),e=a.createRange(d);b=b||c.getBody(d),e.selectNodeContents(b);var f=this.intersection(e),g=0,h=0;return f&&(e.setEnd(f.startContainer,f.startOffset),g=e.toString().length,h=g+f.toString().length,e.detach()),{start:g,end:h,containerNode:b}},moveToBookmark:function(a){var b=a.containerNode,c=0;this.setStart(b,0),this.collapse(!0);var d=[b],e,f=!1,g=!1,h,i,j;while(!g&&(e=d.pop()))if(e.nodeType==3)h=c+e.length,!f&&a.start>=c&&a.start<=h&&(this.setStart(e,a.start-c),f=!0),f&&a.end>=c&&a.end<=h&&(this.setEnd(e,a.end-c),g=!0),c=h;else{j=e.childNodes,i=j.length;while(i--)d.push(j[i])}},getName:function(){return"DomRange"},equals:function(a){return sb.rangesEqual(this,a)},isValid:function(){return X(this)},inspect:function(){return C(this)}}),ob(sb,qb,rb),d.extend(sb,{rangeProperties:cb,RangeIterator:D,copyComparisonConstants:mb,createPrototypeRange:ob,inspect:C,getRangeDocument:s,rangesEqual:function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===b.endOffset}}),a.DomRange=sb,a.RangeException=E}),rangy.createCoreModule("WrappedRange",["DomRange"],function(a,b){var c,d,e=a.dom,f=a.util,g=e.DomPosition,h=a.DomRange,i=e.getBody,j=e.getContentDocument,k=e.isCharacterDataNode;a.features.implementsDomRange&&function(){function k(a){var b=g.length,c;while(b--)c=g[b],a[c]=a.nativeRange[c];a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset}function l(a,b,c,d,e){var f=a.startContainer!==b||a.startOffset!=c,g=a.endContainer!==d||a.endOffset!=e,h=!a.equals(a.nativeRange);if(f||g||h)a.setEnd(d,e),a.setStart(b,c)}function m(a){a.nativeRange.detach(),a.detached=!0;var b=g.length;while(b--)a[g[b]]=null}var d,g=h.rangeProperties,n;c=function(a){if(!a)throw b.createError("WrappedRange: Range must be specified");this.nativeRange=a,k(this)},h.createPrototypeRange(c,l,m),d=c.prototype,d.selectNode=function(a){this.nativeRange.selectNode(a),k(this)},d.cloneContents=function(){return this.nativeRange.cloneContents()},d.surroundContents=function(a){this.nativeRange.surroundContents(a),k(this)},d.collapse=function(a){this.nativeRange.collapse(a),k(this)},d.cloneRange=function(){return new c(this.nativeRange.cloneRange())},d.refresh=function(){k(this)},d.toString=function(){return this.nativeRange.toString()};var o=document.createTextNode("test");i(document).appendChild(o);var p=document.createRange();p.setStart(o,0),p.setEnd(o,0);try{p.setStart(o,1),d.setStart=function(a,b){this.nativeRange.setStart(a,b),k(this)},d.setEnd=function(a,b){this.nativeRange.setEnd(a,b),k(this)},n=function(a){return function(b){this.nativeRange[a](b),k(this)}}}catch(q){d.setStart=function(a,b){try{this.nativeRange.setStart(a,b)}catch(c){this.nativeRange.setEnd(a,b),this.nativeRange.setStart(a,b)}k(this)},d.setEnd=function(a,b){try{this.nativeRange.setEnd(a,b)}catch(c){this.nativeRange.setStart(a,b),this.nativeRange.setEnd(a,b)}k(this)},n=function(a,b){return function(c){try{this.nativeRange[a](c)}catch(d){this.nativeRange[b](c),this.nativeRange[a](c)}k(this)}}}d.setStartBefore=n("setStartBefore","setEndBefore"),d.setStartAfter=n("setStartAfter","setEndAfter"),d.setEndBefore=n("setEndBefore","setStartBefore"),d.setEndAfter=n("setEndAfter","setStartAfter"),d.selectNodeContents=function(a){this.setStartAndEnd(a,0,e.getNodeLength(a))},p.selectNodeContents(o),p.setEnd(o,3);var r=document.createRange();r.selectNodeContents(o),r.setEnd(o,4),r.setStart(o,2),p.compareBoundaryPoints(p.START_TO_END,r)==-1&&p.compareBoundaryPoints(p.END_TO_START,r)==1?d.compareBoundaryPoints=function(a,b){return b=b.nativeRange||b,a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&(a=b.START_TO_END),this.nativeRange.compareBoundaryPoints(a,b)}:d.compareBoundaryPoints=function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)};var s=document.createElement("div");s.innerHTML="123";var t=s.firstChild,u=i(document);u.appendChild(s),p.setStart(t,1),p.setEnd(t,2),p.deleteContents(),t.data=="13"&&(d.deleteContents=function(){this.nativeRange.deleteContents(),k(this)},d.extractContents=function(){var a=this.nativeRange.extractContents();return k(this),a}),u.removeChild(s),u=null,f.isHostMethod(p,"createContextualFragment")&&(d.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)}),i(document).removeChild(o),p.detach(),r.detach(),d.getName=function(){return"WrappedRange"},a.WrappedRange=c,a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),a.createRange()}}();if(a.features.implementsTextRange){var l=function(a){var b=a.parentElement(),c=a.duplicate();c.collapse(!0);var d=c.parentElement();c=a.duplicate(),c.collapse(!1);var f=c.parentElement(),g=d==f?d:e.getCommonAncestor(d,f);return g==b?g:e.getCommonAncestor(b,g)},m=function(a){return a.compareEndPoints("StartToEnd",a)==0},n=function(a,b,c,d,f){var h=a.duplicate();h.collapse(c);var i=h.parentElement();e.isOrIsAncestorOf(b,i)||(i=b);if(!i.canHaveHTML){var j=new g(i.parentNode,e.getNodeIndex(i));return{boundaryPosition:j,nodeInfo:{nodeIndex:j.offset,containerElement:j.node}}}var l=e.getDocument(i).createElement("span");l.parentNode&&l.parentNode.removeChild(l);var m,n=c?"StartToStart":"StartToEnd",o,p,q,r,s=f&&f.containerElement==i?f.nodeIndex:0,t=i.childNodes.length,u=t,v=u;for(;;){v==t?i.appendChild(l):i.insertBefore(l,i.childNodes[v]),h.moveToElementText(l),m=h.compareEndPoints(n,a);if(m==0||s==u)break;if(m==-1){if(u==s+1)break;s=v}else u=u==s+1?s:v;v=Math.floor((s+u)/2),i.removeChild(l)}r=l.nextSibling;if(m==-1&&r&&k(r)){h.setEndPoint(c?"EndToStart":"EndToEnd",a);var w;if(/[\r\n]/.test(r.data)){var x=h.duplicate(),y=x.text.replace(/\r\n/g,"\r").length;w=x.moveStart("character",y);while((m=x.compareEndPoints("StartToEnd",x))==-1)w++,x.moveStart("character",1)}else w=h.text.length;q=new g(r,w)}else o=(d||!c)&&l.previousSibling,p=(d||c)&&l.nextSibling,p&&k(p)?q=new g(p,0):o&&k(o)?q=new g(o,o.data.length):q=new g(i,e.getNodeIndex(l));return l.parentNode.removeChild(l),{boundaryPosition:q,nodeInfo:{nodeIndex:v,containerElement:i}}},o=function(a,b){var c,d,f=a.offset,g=e.getDocument(a.node),h,j,l=i(g).createTextRange(),m=k(a.node);return m?(c=a.node,d=c.parentNode):(j=a.node.childNodes,c=f<j.length?j[f]:null,d=a.node),h=g.createElement("span"),h.innerHTML="&#feff;",c?d.insertBefore(h,c):d.appendChild(h),l.moveToElementText(h),l.collapse(!b),d.removeChild(h),m&&l[b?"moveStart":"moveEnd"]("character",f),l};d=function(a){this.textRange=a,this.refresh()},d.prototype=new h(document),d.prototype.refresh=function(){var a,b,c,d=l(this.textRange);m(this.textRange)?b=a=n(this.textRange,d,!0,!0).boundaryPosition:(c=n(this.textRange,d,!0,!1),a=c.boundaryPosition,b=n(this.textRange,d,!1,!1,c.nodeInfo).boundaryPosition),this.setStart(a.node,a.offset),this.setEnd(b.node,b.offset)},d.prototype.getName=function(){return"WrappedTextRange"},h.copyComparisonConstants(d),d.rangeToTextRange=function(a){if(a.collapsed)return o(new g(a.startContainer,a.startOffset),!0);var b=o(new g(a.startContainer,a.startOffset),!0),c=o(new g(a.endContainer,a.endOffset),!1),d=i(h.getRangeDocument(a)).createTextRange();return d.setEndPoint("StartToStart",b),d.setEndPoint("EndToEnd",c),d},a.WrappedTextRange=d;if(!a.features.implementsDomRange||a.config.preferTextRange){var p=function(){return this}();typeof p.Range=="undefined"&&(p.Range=d),a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),i(a).createTextRange()},a.WrappedRange=d}}a.createRange=function(c){return c=j(c,b,"createRange"),new a.WrappedRange(a.createNativeRange(c))},a.createRangyRange=function(a){return a=j(a,b,"createRangyRange"),new h(a)},a.createIframeRange=function(c){return b.deprecationNotice("createIframeRange()","createRange(iframeEl)"),a.createRange(c)},a.createIframeRangyRange=function(c){return b.deprecationNotice("createIframeRangyRange()","createRangyRange(iframeEl)"),a.createRangyRange(c)},a.addCreateMissingNativeApiListener(function(b){var c=b.document;typeof c.createRange=="undefined"&&(c.createRange=function(){return a.createRange(c)}),c=b=null})}),rangy.createCoreModule("WrappedSelection",["DomRange","WrappedRange"],function(a,b){function r(a){return typeof a=="string"?a=="backward":!!a}function s(a,c){if(!a)return window;if(d.isWindow(a))return a;if(a instanceof T)return a.win;var e=d.getContentDocument(a,b,c);return d.getWindow(e)}function t(a){return s(a,"getWinSelection").getSelection()}function u(a){return s(a,"getDocSelection").document.selection}function I(a,b,c){var d=c?"end":"start",e=c?"start":"end";a.anchorNode=b[d+"Container"],a.anchorOffset=b[d+"Offset"],a.focusNode=b[e+"Container"],a.focusOffset=b[e+"Offset"]}function J(a){var b=a.nativeSelection;a.anchorNode=b.anchorNode,a.anchorOffset=b.anchorOffset,a.focusNode=b.focusNode,a.focusOffset=b.focusOffset}function K(a){a.anchorNode=a.focusNode=null,a.anchorOffset=a.focusOffset=0,a.rangeCount=0,a.isCollapsed=!0,a._ranges.length=0}function L(b){var c;return b instanceof g?(c=a.createNativeRange(b.getDocument()),c.setEnd(b.endContainer,b.endOffset),c.setStart(b.startContainer,b.startOffset)):b instanceof h?c=b.nativeRange:m.implementsDomRange&&b instanceof d.getWindow(b.startContainer).Range&&(c=b),c}function M(a){if(!a.length||a[0].nodeType!=1)return!1;for(var b=1,c=a.length;b<c;++b)if(!d.isAncestorOf(a[0],a[b]))return!1;return!0}function N(a){var c=a.getNodes();if(!M(c))throw b.createError("getSingleElementFromRange: range "+a.inspect()+" did not consist of a single element");return c[0]}function O(a){return!!a&&typeof a.text!="undefined"}function P(a,b){var c=new h(b);a._ranges=[c],I(a,c,!1),a.rangeCount=1,a.isCollapsed=c.collapsed}function Q(b){b._ranges.length=0;if(b.docSelection.type=="None")K(b);else{var c=b.docSelection.createRange();if(O(c))P(b,c);else{b.rangeCount=c.length;var d,e=o(c.item(0));for(var f=0;f<b.rangeCount;++f)d=a.createRange(e),d.selectNode(c.item(f)),b._ranges.push(d);b.isCollapsed=b.rangeCount==1&&b._ranges[0].collapsed,I(b,b._ranges[b.rangeCount-1],!1)}}}function R(a,c){var d=a.docSelection.createRange(),e=N(c),f=o(d.item(0)),g=p(f).createControlRange();for(var h=0,i=d.length;h<i;++h)g.add(d.item(h));try{g.add(e)}catch(j){throw b.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}g.select(),Q(a)}function T(a,b,c){this.nativeSelection=a,this.docSelection=b,this._ranges=[],this.win=c,this.refresh()}function U(a){a.win=a.anchorNode=a.focusNode=a._ranges=null,a.rangeCount=a.anchorOffset=a.focusOffset=0,a.detached=!0}function W(a,b){var c=V.length,d,e;while(c--){d=V[c],e=d.selection;if(b=="deleteAll")U(e);else if(d.win==a)return b=="delete"?(V.splice(c,1),!0):e}return b=="deleteAll"&&(V.length=0),null}function Z(a,c){var d=o(c[0].startContainer),e=p(d).createControlRange();for(var f=0,g,h=c.length;f<h;++f){g=N(c[f]);try{e.add(g)}catch(i){throw b.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}e.select(),Q(a)}function cb(a,b){if(a.win.document!=o(b))throw new i("WRONG_DOCUMENT_ERR")}function db(b){return function(c,d){var e;this.rangeCount?(e=this.getRangeAt(0),e["set"+(b?"Start":"End")](c,d)):(e=a.createRange(this.win.document),e.setStartAndEnd(c,d)),this.setSingleRange(e,this.isBackward())}}function eb(a){var b=[],c=new j(a.anchorNode,a.anchorOffset),d=new j(a.focusNode,a.focusOffset),e=typeof a.getName=="function"?a.getName():"Selection";if(typeof a.rangeCount!="undefined")for(var f=0,h=a.rangeCount;f<h;++f)b[f]=g.inspect(a.getRangeAt(f));return"["+e+"(Ranges: "+b.join(", ")+")(anchor: "+c.inspect()+", focus: "+d.inspect()+"]"}a.config.checkSelectionRanges=!0;var c="boolean",d=a.dom,e=a.util,f=e.isHostMethod,g=a.DomRange,h=a.WrappedRange,i=a.DOMException,j=d.DomPosition,k,l,m=a.features,n="Control",o=d.getDocument,p=d.getBody,q=g.rangesEqual,v=f(window,"getSelection"),w=e.isHostObject(document,"selection");m.implementsWinGetSelection=v,m.implementsDocSelection=w;var x=w&&(!v||a.config.preferTextRange);x?(k=u,a.isSelectionValid=function(a){var b=s(a,"isSelectionValid").document,c=b.selection;return c.type!="None"||o(c.createRange().parentElement())==b}):v?(k=t,a.isSelectionValid=function(){return!0}):b.fail("Neither document.selection or window.getSelection() detected."),a.getNativeSelection=k;var y=k(),z=a.createNativeRange(document),A=p(document),B=e.areHostProperties(y,["anchorNode","focusNode","anchorOffset","focusOffset"]);m.selectionHasAnchorAndFocus=B;var C=f(y,"extend");m.selectionHasExtend=C;var D=typeof y.rangeCount=="number";m.selectionHasRangeCount=D;var E=!1,F=!0;e.areHostMethods(y,["addRange","getRangeAt","removeAllRanges"])&&typeof y.rangeCount=="number"&&m.implementsDomRange&&function(){var a=window.getSelection();if(a){var b=p(document),c=b.appendChild(document.createElement("div"));c.contentEditable="false";var d=c.appendChild(document.createTextNode("\u00a0\u00a0\u00a0")),e=document.createRange();e.setStart(d,1),e.collapse(!0),a.addRange(e),F=a.rangeCount==1,a.removeAllRanges();var f=e.cloneRange();e.setStart(d,0),f.setEnd(d,3),f.setStart(d,2),a.addRange(e),a.addRange(f),E=a.rangeCount==2,b.removeChild(c),a.removeAllRanges(),e.detach(),f.detach()}}(),m.selectionSupportsMultipleRanges=E,m.collapsedNonEditableSelectionsSupported=F;var G=!1,H;A&&f(A,"createControlRange")&&(H=A.createControlRange(),e.areHostProperties(H,["item","add"])&&(G=!0)),m.implementsControlRange=G,B?l=function(a){return a.anchorNode===a.focusNode&&a.anchorOffset===a.focusOffset}:l=function(a){return a.rangeCount?a.getRangeAt(a.rangeCount-1).collapsed:!1};var S;f(y,"getRangeAt")?S=function(a,b){try{return a.getRangeAt(b)}catch(c){return null}}:B&&(S=function(b){var c=o(b.anchorNode),d=a.createRange(c);return d.setStartAndEnd(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset),d.collapsed!==this.isCollapsed&&d.setStartAndEnd(b.focusNode,b.focusOffset,b.anchorNode,b.anchorOffset),d}),T.prototype=a.selectionPrototype;var V=[],X=function(a){if(a&&a instanceof T)return a.refresh(),a;a=s(a,"getNativeSelection");var b=W(a),c=k(a),d=w?u(a):null;return b?(b.nativeSelection=c,b.docSelection=d,b.refresh()):(b=new T(c,d,a),V.push({win:a,selection:b})),b};a.getSelection=X,a.getIframeSelection=function(c){return b.deprecationNotice("getIframeSelection()","getSelection(iframeEl)"),a.getSelection(d.getIframeWindow(c))};var Y=T.prototype;if(!x&&B&&e.areHostMethods(y,["removeAllRanges","addRange"])){Y.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),K(this)};var $=function(b,c){var d=g.getRangeDocument(c),e=a.createRange(d);e.collapseToPoint(c.endContainer,c.endOffset),b.nativeSelection.addRange(L(e)),b.nativeSelection.extend(c.startContainer,c.startOffset),b.refresh()};D?Y.addRange=function(b,c){if(G&&w&&this.docSelection.type==n)R(this,b);else if(r(c)&&C)$(this,b);else{var d;E?d=this.rangeCount:(this.removeAllRanges(),d=0),this.nativeSelection.addRange(L(b).cloneRange()),this.rangeCount=this.nativeSelection.rangeCount;if(this.rangeCount==d+1){if(a.config.checkSelectionRanges){var e=S(this.nativeSelection,this.rangeCount-1);e&&!q(e,b)&&(b=new h(e))}this._ranges[this.rangeCount-1]=b,I(this,b,bb(this.nativeSelection)),this.isCollapsed=l(this)}else this.refresh()}}:Y.addRange=function(a,b){r(b)&&C?$(this,a):(this.nativeSelection.addRange(L(a)),this.refresh())},Y.setRanges=function(a){if(G&&a.length>1)Z(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;b<c;++b)this.addRange(a[b])}}}else{if(!(f(y,"empty")&&f(z,"select")&&G&&x))return b.fail("No means of selecting a Range or TextRange was found"),!1;Y.removeAllRanges=function(){try{this.docSelection.empty();if(this.docSelection.type!="None"){var a;if(this.anchorNode)a=o(this.anchorNode);else if(this.docSelection.type==n){var b=this.docSelection.createRange();b.length&&(a=o(b.item(0)))}if(a){var c=p(a).createTextRange();c.select(),this.docSelection.empty()}}}catch(d){}K(this)},Y.addRange=function(b){this.docSelection.type==n?R(this,b):(a.WrappedTextRange.rangeToTextRange(b).select(),this._ranges[0]=b,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,I(this,b,!1))},Y.setRanges=function(a){this.removeAllRanges();var b=a.length;b>1?Z(this,a):b&&this.addRange(a[0])}}Y.getRangeAt=function(a){if(a<0||a>=this.rangeCount)throw new i("INDEX_SIZE_ERR");return this._ranges[a].cloneRange()};var _;if(x)_=function(b){var c;a.isSelectionValid(b.win)?c=b.docSelection.createRange():(c=p(b.win.document).createTextRange(),c.collapse(!0)),b.docSelection.type==n?Q(b):O(c)?P(b,c):K(b)};else if(f(y,"getRangeAt")&&typeof y.rangeCount=="number")_=function(b){if(G&&w&&b.docSelection.type==n)Q(b);else{b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount;if(b.rangeCount){for(var c=0,d=b.rangeCount;c<d;++c)b._ranges[c]=new a.WrappedRange(b.nativeSelection.getRangeAt(c));I(b,b._ranges[b.rangeCount-1],bb(b.nativeSelection)),b.isCollapsed=l(b)}else K(b)}};else{if(!B||typeof y.isCollapsed!=c||typeof z.collapsed!=c||!m.implementsDomRange)return b.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;_=function(a){var b,c=a.nativeSelection;c.anchorNode?(b=S(c,0),a._ranges=[b],a.rangeCount=1,J(a),a.isCollapsed=l(a)):K(a)}}Y.refresh=function(a){var b=a?this._ranges.slice(0):null,c=this.anchorNode,d=this.anchorOffset;_(this);if(a){var e=b.length;if(e!=this._ranges.length)return!0;if(this.anchorNode!=c||this.anchorOffset!=d)return!0;while(e--)if(!q(b[e],this._ranges[e]))return!0;return!1}};var ab=function(a,b){var c=a.getAllRanges();a.removeAllRanges();for(var d=0,e=c.length;d<e;++d)q(b,c[d])||a.addRange(c[d]);a.rangeCount||K(a)};G?Y.removeRange=function(a){if(this.docSelection.type==n){var b=this.docSelection.createRange(),c=N(a),d=o(b.item(0)),e=p(d).createControlRange(),f,g=!1;for(var h=0,i=b.length;h<i;++h)f=b.item(h),f!==c||g?e.add(b.item(h)):g=!0;e.select(),Q(this)}else ab(this,a)}:Y.removeRange=function(a){ab(this,a)};var bb;!x&&B&&m.implementsDomRange?(bb=function(a){var b=!1;return a.anchorNode&&(b=d.comparePoints(a.anchorNode,a.anchorOffset,a.focusNode,a.focusOffset)==1),b},Y.isBackward=function(){return bb(this)}):bb=Y.isBackward=function(){return!1},Y.isBackwards=Y.isBackward,Y.toString=function(){var a=[];for(var b=0,c=this.rangeCount;b<c;++b)a[b]=""+this._ranges[b];return a.join("")},Y.collapse=function(b,c){cb(this,b);var d=a.createRange(b);d.collapseToPoint(b,c),this.setSingleRange(d),this.isCollapsed=!0},Y.collapseToStart=function(){if(!this.rangeCount)throw new i("INVALID_STATE_ERR");var a=this._ranges[0];this.collapse(a.startContainer,a.startOffset)},Y.collapseToEnd=function(){if(!this.rangeCount)throw new i("INVALID_STATE_ERR");var a=this._ranges[this.rangeCount-1];this.collapse(a.endContainer,a.endOffset)},Y.selectAllChildren=function(b){cb(this,b);var c=a.createRange(b);c.selectNodeContents(b),console.log("before",c.inspect()),this.setSingleRange(c),console.log("after",this._ranges[0].inspect())},Y.deleteFromDocument=function(){if(G&&w&&this.docSelection.type==n){var a=this.docSelection.createRange(),b;while(a.length)b=a.item(0),a.remove(b),b.parentNode.removeChild(b);this.refresh()}else if(this.rangeCount){var c=this.getAllRanges();if(c.length){this.removeAllRanges();for(var d=0,e=c.length;d<e;++d)c[d].deleteContents();this.addRange(c[e-1])}}},Y.eachRange=function(a,b){for(var c=0,d=this._ranges.length;c<d;++c)if(a(this.getRangeAt(c)))return b},Y.getAllRanges=function(){var a=[];return this.eachRange(function(b){a.push(b)}),a},Y.setSingleRange=function(a,b){this.removeAllRanges(),this.addRange(a,b)},Y.callMethodOnEachRange=function(a,b){var c=[];return this.eachRange(function(d){c.push(d[a].apply(d,b))}),c},Y.setStart=db(!0),Y.setEnd=db(!1),a.rangePrototype.select=function(a){X(this.getDocument()).setSingleRange(this,a)},Y.changeEachRange=function(a){var b=[],c=this.isBackward();this.eachRange(function(c){a(c),b.push(c)}),this.removeAllRanges(),c&&b.length==1?this.addRange(b[0],"backward"):this.setRanges(b)},Y.containsNode=function(a,b){return this.eachRange(function(c){return c.containsNode(a,b)},!0)},Y.getBookmark=function(a){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[a])}},Y.moveToBookmark=function(b){var c=[];for(var d=0,e,f;e=b.rangeBookmarks[d++];)f=a.createRange(this.win),f.moveToBookmark(e),c.push(f);b.backward?this.setSingleRange(c[0],"backward"):this.setRanges(c)},Y.toHtml=function(){return this.callMethodOnEachRange("toHtml").join("")},Y.getName=function(){return"WrappedSelection"},Y.inspect=function(){return eb(this)},Y.detach=function(){W(this.win,"delete"),U(this)},T.detachAll=function(){W(null,"deleteAll")},T.inspect=eb,T.isDirectionBackward=r,a.Selection=T,a.selectionPrototype=Y,a.addCreateMissingNativeApiListener(function(a){typeof a.getSelection=="undefined"&&(a.getSelection=function(){return X(a)}),a=null})})