rangy-core.js 142 KB

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