From 930700f68c5d2ece214e4f8f78c1b05b9401b5dc Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 3 Sep 2013 18:25:32 -0700 Subject: [PATCH] IE 9 compat; isParented; beginning of isRemoved --- packages/ui/domrange.js | 73 +++++++++++++++++++++++++++++++++-- packages/ui/domrange_tests.js | 71 ++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 3 deletions(-) diff --git a/packages/ui/domrange.js b/packages/ui/domrange.js index a6988c7cf5..a49a9dd026 100644 --- a/packages/ui/domrange.js +++ b/packages/ui/domrange.js @@ -65,7 +65,7 @@ var checkId = function (id) { throw new Error("id may not be empty"); }; -var textExpandosOk = (function () { +var textExpandosSupported = (function () { var tn = document.createTextNode(''); try { tn.blahblah = true; @@ -77,10 +77,57 @@ var textExpandosOk = (function () { })(); var createMarkerNode = ( - textExpandosOk ? + textExpandosSupported ? function () { return document.createTextNode(""); } : function () { return document.createComment(""); }); +var rangeParented = function (range) { + if (! range.isParented) { + range.isParented = true; + + if (! range.owner) { + // top-level (unowned) ranges in an element, + // keep a pointer to the range on the parent + // element. This is really just for IE 9+ + // TextNode GC issues, but we can't do reliable + // bug detection. + var parentNode = range.parentNode(); + var rangeDict = ( + parentNode.$_uiranges || + (parentNode.$_uiranges = {})); + rangeDict[range._rangeId] = range; + range._rangeDict = rangeDict; + } + + // recurse on member ranges + var members = range.members; + for (var k in members) { + var mem = members[k]; + if ('dom' in mem) + rangeParented(mem.dom); + } + } +}; + +var rangeRemoved = function (range) { + if (! range.isRemoved) { + range.isRemoved = true; + + if (range._rangeDict) + delete range._rangeDict[range._rangeId]; + + // recurse on member ranges + var members = range.members; + for (var k in members) { + var mem = members[k]; + if ('dom' in mem) + rangeRemoved(mem.dom); + } + } +}; + +var nextGuid = 1; + var DomRange = function (component) { // This code supports IE 8 if `createTextNode` is changed // to `createComment`. What we really should do is: @@ -113,6 +160,11 @@ var DomRange = function (component) { this.members = {}; this.nextMemberId = 1; this.owner = null; + this._rangeId = nextGuid++; + this._rangeDict = null; + + this.isParented = false; + this.isRemoved = false; }; _extend(DomRange.prototype, { @@ -142,6 +194,13 @@ _extend(DomRange.prototype, { for (var i = 0, N = nodes.length; i < N; i++) removeNode(nodes[i]); + // call `rangeRemoved` on each member range + var members = this.members; + for (var k in members) { + var mem = members[k]; + if ('dom' in mem) + rangeRemoved(mem.dom); + } this.members = {}; }, // (_nextNode is internal) @@ -202,6 +261,7 @@ _extend(DomRange.prototype, { if (oldRange.start.parentNode !== parentNode) { delete members[id]; oldRange.owner = null; + rangeRemoved(oldRange); } else { throw new Error("Member already exists: " + id.slice(1)); } @@ -231,6 +291,9 @@ _extend(DomRange.prototype, { members[id] = newMember; for (var i = 0; i < nodes.length; i++) insertNode(nodes[i], parentNode, nextNode); + + if (this.isParented) + rangeParented(range); } else { // Node if (typeof newMember.nodeType !== 'number') @@ -257,6 +320,7 @@ _extend(DomRange.prototype, { removeNode(this.start); removeNode(this.end); this.owner = null; + rangeRemoved(this); return; } @@ -349,7 +413,7 @@ _extend(DomRange.prototype, { var members = this.members; var parentNode = this.parentNode(); for (var k in members) { - // me, is a DomRange or node + // mem is a component (hosting a Range) or a Node var mem = members[k]; if ('dom' in mem) { // Range @@ -359,6 +423,7 @@ _extend(DomRange.prototype, { } else { range.owner = null; delete members[k]; // gone + rangeRemoved(range); } } else { // Node @@ -562,6 +627,7 @@ _extend(DomRange.prototype, { return range.start; } else { range.owner = null; + rangeRemoved(range); } } else { // Node @@ -629,6 +695,7 @@ DomRange.insert = function (component, parentNode, nextNode) { parentNode = makeOrFindTbody(parentNode, nextNode); for (var i = 0; i < nodes.length; i++) insertNode(nodes[i], parentNode, nextNode); + rangeParented(range); }; ///// TBODY FIX for compatibility with jQuery. diff --git a/packages/ui/domrange_tests.js b/packages/ui/domrange_tests.js index b5776780ae..4fcddddd82 100644 --- a/packages/ui/domrange_tests.js +++ b/packages/ui/domrange_tests.js @@ -932,6 +932,77 @@ Tinytest.add("ui - DomRange - nested event order", function (test) { }); }); +Tinytest.add("ui - DomRange - isParented", function (test) { + inDocument(new DomRange, function (r) { + test.equal(r.isParented, true); + var a = new DomRange; + var b = new DomRange; + var c = new DomRange; + var d = new DomRange; + var e = new DomRange; + var abcde = function (ap, bp, cp, dp, ep) { + test.equal(!! a.isParented, !! ap); + test.equal(!! b.isParented, !! bp); + test.equal(!! c.isParented, !! cp); + test.equal(!! d.isParented, !! dp); + test.equal(!! e.isParented, !! ep); + }; + var div = document.createElement("DIV"); + c.add(div); + abcde(0, 0, 0, 0, 0); + d.add(e); + abcde(0, 0, 0, 0, 0); + DomRange.insert(d, div); + abcde(0, 0, 0, 1, 1); + a.add(b); + abcde(0, 0, 0, 1, 1); + r.add(a); + abcde(1, 1, 0, 1, 1); + b.add(c); + abcde(1, 1, 1, 1, 1); + + var container = r.parentNode(); + test.equal(_.keys(container.$_uiranges).length, 1); + test.equal(_.keys(div.$_uiranges).length, 1); + d.remove(); + test.equal(_.keys(div.$_uiranges).length, 0); + r.remove(); + test.equal(_.keys(container.$_uiranges).length, 0); + }); +}); + +Tinytest.add("ui - DomRange - structural removal", function (test) { + inDocument(new DomRange, function (r) { + var a = new DomRange; + test.isFalse(a.isRemoved); + r.add('a', a); + test.isFalse(a.isRemoved); + r.remove('a'); + test.isTrue(a.isRemoved); + + var b = new DomRange; + test.isFalse(b.isRemoved); + r.add(b); + test.isFalse(b.isRemoved); + r.removeAll(); + test.isTrue(b.isRemoved); + + var c = new DomRange; + var d = new DomRange; + var e = new DomRange; + c.add(d); + d.add(e); + r.add('c', c); + test.isFalse(c.isRemoved); + test.isFalse(d.isRemoved); + test.isFalse(e.isRemoved); + r.remove('c'); + test.isTrue(c.isRemoved); + test.isTrue(d.isRemoved); + test.isTrue(e.isRemoved); + }); +}); + // TO TEST STILL: // - external remove element // - double-add, double-remove