adium-1.4 2642:05dc1d90a882: Bring our coalescing WKMV improveme...

commits at adium.im commits at adium.im
Sat Oct 24 20:55:06 UTC 2009


details:	http://hg.adium.im/adium-1.4/rev/05dc1d90a882
revision:	2642:05dc1d90a882
author:		Stephen Holt <sholt at adium.im>
date:		Sat Oct 24 16:54:14 2009 -0400

Bring our coalescing WKMV improvements to the 1.4 branch.

diffs (253 lines):

diff -r 18edcaf334d1 -r 05dc1d90a882 Plugins/WebKit Message View/Template.html
--- a/Plugins/WebKit Message View/Template.html	Sat Oct 24 16:47:32 2009 -0400
+++ b/Plugins/WebKit Message View/Template.html	Sat Oct 24 16:54:14 2009 -0400
@@ -15,62 +15,187 @@
 			var documentFragment = range.createContextualFragment(html);
 			node.appendChild(documentFragment);
 		}
+		
+		// a coalesced HTML object buffers and outputs DOM objects en masse.
+		// saves A LOT of CSS recalculation time when loading many messages.
+		// (ex. a long twitter timeline)
+		function CoalescedHTML() {
+			var self = this;
+			this.fragment = document.createDocumentFragment();
+			this.timeoutID = 0;
+			this.coalesceRounds = 0;
+			this.isCoalescing = false;
+			this.isConsecutive = undefined;
+			this.shouldScroll = undefined;
+			
+			function outputHTML() {
+				var insert = document.getElementById("insert");
+				if(!!insert && self.isConsecutive) {
+					insert.parentNode.replaceChild(self.fragment, insert);
+				} else {
+					if(insert)
+						insert.parentNode.removeChild(insert);
+					// insert the documentFragment into the live DOM
+					document.getElementById("Chat").appendChild(self.fragment);
+				}
+				alignChat(self.shouldScroll);
+				
+				// reset state to empty/non-coalescing
+				self.shouldScroll = undefined;
+				self.isConsecutive = undefined;
+				self.isCoalescing = false;
+				self.coalesceRounds = 0;
+			}
+			
+			// creates and returns a new documentFragment, containing all content nodes
+			// which can be inserted as a single node.
+			function createHTMLNode(html) {
+				var range = document.createRange();
+				range.selectNode(document.getElementById("Chat"));
+				return range.createContextualFragment(html);
+			}
+			
+			// removes first insert node from the internal fragment.
+			function rmInsertNode() {
+				var insert = self.fragment.querySelector("#insert");
+				if(insert)
+					insert.parentNode.removeChild(insert);
+			}
+			
+			// (re)start the coalescing timer.
+			//   we wait 25ms for a new message to come in.
+			//   If we get one, restart the timer and wait another 10ms.
+			//   If not, run outputHTML()
+			//  We do this a maximum of 400 times, for 10s max that can be spent
+			//  coalescing input, since this will block display.
+			this.coalesce = function() {
+				window.clearTimeout(self.timeoutID);
+				self.timeoutID = window.setTimeout(outputHTML, 25);
+				self.isCoalescing = true;
+				self.coalesceRounds += 1;
+				if(400 < self.coalesceRounds)
+					self.cancel();
+			}
+			
+			// if we need to append content into an insertion div,
+			// we need to clear the buffer and cancel the timeout.
+			this.cancel = function() {
+				if(self.isCoalescing) {
+					window.clearTimeout(self.timeoutID);
+					outputHTML();
+				}
+			}
+			
+			
+			// coalased analogs to the global functions
+			
+			this.append = function(html, shouldScroll) {
+				// if we started this fragment with a consecuative message,
+				// cancel and output before we continue
+				if(self.isConsecutive) {
+					self.cancel();
+				}
+				self.isConsecutive = false;
+				rmInsertNode();
+				var node = createHTMLNode(html);
+				self.fragment.appendChild(node);
+				
+				node = null;
+
+				if(shouldScroll) self.shouldScroll = shouldScroll;
+				self.coalesce();
+			}
+			
+			this.appendNext = function(html, shouldScroll) {
+				if(undefined === self.isConsecutive)
+					self.isConsecutive = true;
+				var node = createHTMLNode(html);
+				var insert = self.fragment.querySelector("#insert");
+				if(insert) {
+					insert.parentNode.replaceChild(node, insert);
+				} else {
+					self.fragment.appendChild(node);
+				}
+				node = null;
+				if(shouldScroll)
+					self.shouldScroll = shouldScroll;
+				self.coalesce();
+			}
+			
+			this.replaceLast = function (html, shouldScroll) {
+				rmInsertNode();
+				var node = createHTMLNode(html);
+				var lastMessage = self.fragment.lastChild;
+				lastMessage.parentNode.replaceChild(node, lastMessage);
+				node = null;
+				if(shouldScroll)
+					self.shouldScroll = shouldScroll;
+			}
+		}
+		var coalescedHTML;
 
 		//Appending new content to the message view
 		function appendMessage(html) {
-			var shouldScroll = nearBottom();
-			appendMessageNoScroll(html);
-			alignChat(shouldScroll);
+			var shouldScroll;
+			
+			// Only call nearBottom() if should scroll is undefined.
+			if(undefined === coalescedHTML.shouldScroll) {
+				shouldScroll = nearBottom();
+			} else {
+				shouldScroll = coalescedHTML.shouldScroll;
+			}
+			appendMessageNoScroll(html, shouldScroll);
 		}
 		
-		function appendMessageNoScroll(html) {			
-			//Remove the current insertion point
-			var insert = document.getElementById("insert");
-			if(insert) 
-				insert.parentNode.removeChild(insert);
-			
-			appendHTML(html);
+		function appendMessageNoScroll(html, shouldScroll) {			
+			shouldScroll = shouldScroll || false;
+			// always try to coalesce new, non-griuped, messages
+			coalescedHTML.append(html, shouldScroll)
 		}
 		
 		function appendNextMessage(html){
-			var shouldScroll = nearBottom();
-			appendNextMessageNoScroll(html);
-			alignChat(shouldScroll);
+			var shouldScroll;
+			if(undefined === coalescedHTML.shouldScroll) {
+				shouldScroll = nearBottom();
+			} else {
+				shouldScroll = coalescedHTML.shouldScroll;
+			}
+			appendNextMessageNoScroll(html, shouldScroll);
 		}
 		
-		function appendNextMessageNoScroll(html){
-			//Locate the insertion point
-			var insert = document.getElementById("insert");
-			if(insert){
-				//make new node
-				var range = document.createRange();
-				range.selectNode(insert.parentNode);
-				var newNode = range.createContextualFragment(html);
-
-				//swap
-				insert.parentNode.replaceChild(newNode,insert);
-			} else {
-				appendMessageNoScroll(html);
-			}
+		function appendNextMessageNoScroll(html, shouldScroll){
+			shouldScroll = shouldScroll || false;
+			// only group next messages if we're already coalescing input
+			coalescedHTML.appendNext(html, shouldScroll);
 		}
 
 		function replaceLastMessage(html){
-			shouldScroll = nearBottom();
+			var shouldScroll;
+			// only replace messages if we're already coalescing
+			if(coalescedHTML.isCoalescing){
+				if(undefined === coalescedHTML.shouldScroll) {
+					shouldScroll = nearBottom();
+				} else {
+					shouldScroll = coalescedHTML.shouldScroll;
+				}
+				coalescedHTML.replaceLast(html, shouldScroll);
+			} else {
+				shouldScroll = nearBottom();
+				//Retrieve the current insertion point, then remove it
+				//This requires that there have been an insertion point... is there a better way to retrieve the last element? -evands
+				var insert = document.getElementById("insert");
+				if(insert){
+					var parentNode = insert.parentNode;
+					parentNode.removeChild(insert);
+					var lastMessage = document.getElementById("Chat").lastChild;
+					document.getElementById("Chat").removeChild(lastMessage);
+				}
 
-			//Retrieve the current insertion point, then remove it
-			//This requires that there have been an insertion point... is there a better way to retrieve the last element? -evands
-			var insert = document.getElementById("insert");
-			if(insert){
-				var parentNode = insert.parentNode;
-				parentNode.removeChild(insert);
-				var lastMessage = document.getElementById("Chat").lastChild;
-				document.getElementById("Chat").removeChild(lastMessage);
+				//Now append the message itself
+				appendHTML(html);
+
+				alignChat(shouldScroll);
 			}
-
-			//Now append the message itself
-			appendHTML(html);
-
-			alignChat(shouldScroll);
 		}
 
 		//Auto-scroll to bottom.  Use nearBottom to determine if a scrollToBottom is desired.
@@ -168,6 +293,11 @@
 		window.onresize = function windowDidResize(){
 			alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs
 		}
+		
+		function adiumOnLoad() {
+			alignChat(true);
+			coalescedHTML = new CoalescedHTML();
+		}
 	</script>
 
 	<style type="text/css">
@@ -190,7 +320,7 @@
 	</style>
 
 </head>
-<body onload="alignChat(true);" style="==bodyBackground==">
+<body onload="adiumOnLoad();" style="==bodyBackground==">
 %@
 <div id="Chat">
 </div>




More information about the commits mailing list