wysihtml5_with_ps 0.0.1

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