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

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