From 2e2ee290565ed1230d1ed02deb85ebc5328a4fbf Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 23 Jul 2012 16:30:28 -0700 Subject: [PATCH] porting GC logic to livedocument --- packages/liveui/livedocument.js | 45 +++++++++++++++++++++++++++ packages/liveui/livedocument_tests.js | 15 +++++++-- packages/liveui/liveui.js | 6 ++-- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/packages/liveui/livedocument.js b/packages/liveui/livedocument.js index d2e471da84..c5f6e353a8 100644 --- a/packages/liveui/livedocument.js +++ b/packages/liveui/livedocument.js @@ -11,6 +11,7 @@ Meteor.ui._doc = Meteor.ui._doc || {}; var MARKER_PARSE_REGEX = /^LIVEUI_(.*)$/; Meteor.ui._TAG = "_liveui"; + Meteor.ui._HELD = "_liveui_refs"; // XXX _liveui_held Meteor.ui._doc._newAnnotations = {}; // {id -> options} until range created Meteor.ui._doc._newRanges = []; // [LiveRange, ...] until flush time @@ -113,6 +114,8 @@ Meteor.ui._doc = Meteor.ui._doc || {}; var range = new Meteor.ui._LiveRange(Meteor.ui._TAG, subFrag); // assign options to the LiveRange, including `id` _.extend(range, options); + // enqueue the new range for onscreen callback processing + Meteor.ui._doc._newRanges.push(range); var next = comment.nextSibling; @@ -138,6 +141,11 @@ Meteor.ui._doc = Meteor.ui._doc || {}; return frag; }; + // at flush time, doCallbacks() + var cx = new Meteor.deps.Context; + cx.on_invalidate(Meteor.ui._doc._doCallbacks); + cx.invalidate(); + return makeFrag(topHtml); }; @@ -156,4 +164,41 @@ Meteor.ui._doc = Meteor.ui._doc || {}; html + ""); }; + Meteor.ui._doc.poke = function(range) { + // XXX like Sarge.checkOffscreen + }; + + Meteor.ui._doc.cleanNodes = function(start, end) { + // XXX like Sarge.shuck + }; + + Meteor.ui._doc._doCallbacks = function() { + _.each(Meteor.ui._doc._newRanges, function(range) { + if (range.onscreen) { + var node = range.firstNode(); + if (! (node.parentNode && + (Meteor.ui._isNodeOnscreen(node) || + Meteor.ui._doc._isNodeHeld(node)))) + return; // range was created but isn't in the document at flush time + range.onscreen(); + // existence of `killed=false` means range went onscreen and we'll + // have to call offscreen() later + range.killed = false; + } + }); + + Meteor.ui._doc._newAnnotations = {}; + Meteor.ui._doc._newRanges.length = 0; + }; + + // "node holding" is a facility used for unit tests where + // we don't GC a DocumentFragment at flush time. + Meteor.ui._doc._isNodeHeld = function(node) { + while (node.parentNode) + node = node.parentNode; + + return node.nodeType !== 3 /*TEXT_NODE*/ && + node[Meteor.ui._HELD]; + }; + })(); \ No newline at end of file diff --git a/packages/liveui/livedocument_tests.js b/packages/liveui/livedocument_tests.js index 30eb642d3b..0f709b5727 100644 --- a/packages/liveui/livedocument_tests.js +++ b/packages/liveui/livedocument_tests.js @@ -1,9 +1,12 @@ Tinytest.add("livedocument - assembly", function(test) { var doTest = function(calc) { + var onscreens = []; var frag = Meteor.ui._doc.materialize( calc(function(str, expected) { - return Meteor.ui._doc.annotate(str); + return Meteor.ui._doc.annotate(str, {onscreen:function() { + onscreens.push(this.id); + }}); })); var groups = []; var html = calc(function(str, expected, noRange) { @@ -13,7 +16,8 @@ Tinytest.add("livedocument - assembly", function(test) { groups.push(str); return str; }); - test.equal(WrappedFrag(frag).html(), html); + var f = WrappedFrag(frag); + test.equal(f.html(), html); var actualGroups = []; var tempRange = new Meteor.ui._LiveRange(Meteor.ui._TAG, frag); @@ -22,6 +26,13 @@ Tinytest.add("livedocument - assembly", function(test) { actualGroups.push(Meteor.ui._rangeToHtml(rng)); }); test.equal(actualGroups.join(','), groups.join(',')); + + f.hold(); + Meteor.flush(); + test.equal(onscreens.length, groups.length); + var uniqueOnscreens = _.uniq(onscreens); + test.equal(uniqueOnscreens.length, onscreens.length); + f.release(); }; doTest(function(A) { return "

Hello

"; }); diff --git a/packages/liveui/liveui.js b/packages/liveui/liveui.js index 7c8d06c9c6..b8235a7ccd 100644 --- a/packages/liveui/liveui.js +++ b/packages/liveui/liveui.js @@ -8,7 +8,7 @@ Meteor.ui = Meteor.ui || {}; if (typeof html_func !== "function") throw new Error("Meteor.ui.render() requires a function as its first argument."); - if (Meteor.ui._inRenderMode) + if (Materializer.current) throw new Error("Can't nest Meteor.ui.render."); return renderChunk(html_func, options, "fragment").containerNode(); @@ -323,7 +323,7 @@ Meteor.ui = Meteor.ui || {}; renderChunk(html_func, options, "patch", this); }); }; - range.destroy = function() { + range.destroyed = function() { range.context && range.context.invalidate(); callOffscreen(); }; @@ -463,7 +463,7 @@ Meteor.ui = Meteor.ui || {}; range.killed = true; // only one of these ever scheduled per range: Sarge.atFlushTime(function() { - range.destroy && range.destroy(); + range.destroyed && range.destroyed(); }); } },