can3p (can3p) wrote in changelog,
can3p
can3p
changelog

[ljcom] r11460: LJSUP-11155: Dynamic iframe for tackthis...

Committer: dpetrov
LJSUP-11155: Dynamic iframe for tackthis tag
A   trunk/htdocs/js/lj.postmessage.js
A   trunk/htdocs/js/lj.rpc.js
U   trunk/htdocs/misc/xdr.html
Added: trunk/htdocs/js/lj.postmessage.js
===================================================================
--- trunk/htdocs/js/lj.postmessage.js	                        (rev 0)
+++ trunk/htdocs/js/lj.postmessage.js	2012-02-09 14:06:53 UTC (rev 11460)
@@ -0,0 +1,463 @@
+/*!
+ * LiveJournal crossdomain messaging plugin plugin.
+ * @author Dmitry Petrov <Dmitry.Petrov@sup.com>, 2011-2012
+ *
+ * Plugin is used to allow crossdomain communication between container window and child
+ * popups or frames. To make communication work in older browsers, user has to put html
+ * (xdr.html) file in the root of container page domain.
+ *
+ * To allow communication either container should initiate communication or child popup
+ * should know the origin of the container ( protocol://host:port ).
+ *
+ * Contents of xdr.html:
+ * <!DOCTYPE HTML>
+ * <html lang="en">
+ * <head>
+ *     <meta charset="UTF-8">
+ *     <script type="text/javascript">
+ *         function sendData() {
+ *             if ( location.hash && location.hash.length > 1 ) {
+ *                 window.parent.opener.rpcMessage = location.hash;
+ *                 location.hash = '#';
+ *             }
+ *         }
+ * 
+ *         setInterval( sendData, 50 );
+ *     </script>
+ * </head>
+ * <body>
+ * </body>
+ * </html>
+ *
+ * Plugin was tested to work in IE7+, Firefox 3.5+, Opera 10.10+, Google Chrome 12+
+ *
+ * @TODO: Plugin doesn't check origin of the messages for now, though it's not possible for old
+ * browser because of technology used to pass data ( hash polling ).
+ *
+ */
+(function( window, $ ){
+		var cache_bust = 1,
+			interval_id,
+
+		/**
+		 * window name is used to identify child window from container side.
+		 */
+		windowId = window.name,
+
+		/**
+		 * has_postMessage shows if browser supports postMessage communication. IE8 is disabled because it
+		 * doesn't allow to use postMessage from popup.
+		 * For frames, postMessage should be enabled from frames.
+		 */
+		isIE8 = $.browser.msie && parseInt( $.browser.version, 10 ) === 8,
+		isPopup = !!window.opener,
+		has_postMessage = !!window.postMessage && ( !$.browser.msie || parseInt( $.browser.version, 10 ) > 8 ),
+
+		// isIE8 = true, 
+		// isPopup = !!window.opener,
+		// has_postMessage = false,
+		/**
+		 * senderUrl stores the address of container window
+		 */
+		senderUrl,
+		/**
+		 * iframeUrl stores the address of iframe in the domain of container window
+		 */
+		iframeUrl,
+		receiveCallbacks = [],
+		senderDomainFrame,
+		/*
+		 * pollDelay is a delay for checking it hash changed
+		 */
+		pollDelay = 100,
+		/*
+		 * Buffer stores messages that could no be delivered because the url of parent window
+		 * was not set. Array is used only in cases when the fallback method is used instead
+		 * of postMessage.
+		 */
+		unsentMessages = [];
+
+	/**
+	 * targets is a collection of targets to exhange messages with. Container can pass messages to the multiple domains
+	 */
+	var targets = function() {
+		var container = [];
+
+		var collection = {
+			add: function( w, ref, isFrame ) {
+				var pos;
+				if( ( pos = collection.indexOf( w ) === -1 ) ){
+					container.push( { win: w, href: ref, isFrame: isFrame } );
+				} else {
+					collection[ pos ].href = ref;
+					collection[ pos ].isFrame = isFrame;
+				}
+			},
+
+			remove: function( w ) {
+				var pos;
+				if( ( pos = collection( w ) === -1 ) ){
+					return;
+				} else {
+					collection.splice( pos, 1 );
+					collection[ pos ].href = ref;
+				}
+			},
+
+			indexOf: function( w ) {
+				for( var i = 0; i < collection.length; ++i ) {
+					if( w === collection[ i ].top ) {
+						return i;
+					}
+				}
+
+				return -1;
+			},
+
+			getTarget: function( win ) {
+				var pos = collection.indexOf( win );
+				if( pos === -1 ) {
+					return;
+				} else {
+					return container[ pos ];
+				}
+			}
+		};
+
+		return collection;
+	}();
+
+	function createPacket(message) {
+		// packet = typeof message === 'string' ? { data: message } : message
+		return { frameId: windowId, message: message };
+	}
+
+	/**
+	 * Converts message object to the hash string ready to pass to other window
+	 *
+	 * @param {Object} message
+	 * @return {String}
+	 */
+	function toHash( message ) {
+		var params = jQuery.param(createPacket(message));
+		return '#' + (+new Date) + (cache_bust++) + ':rpc&' + params;
+	}
+
+	/**
+	 * Converts a string that was serialized with jQuery.param back to the object.
+	 *
+	 * @param {String} str
+	 *
+	 * @return {Object}
+	 */
+	function toObject(str) {
+		var obj = {}, pair;
+		var pairs = decodeURIComponent(str).split( "&" );
+		var injectParam = function(key, val) {
+			var firstBracket = key.indexOf('[');
+
+			if (firstBracket === -1) {
+				obj[key] = val;
+				return;
+			}
+
+			var prevkey = key.substring(0, firstBracket),
+				key = key.substr(firstBracket),
+				prev = obj,
+				newobj,
+				newkey;
+
+			key.replace(/\[([^\]]+)?\]/g, function(chunk, idx, pos) {
+				var newobj, newkey;
+				if (chunk.match(/\[\d*\]/)) {
+					newobj = prev[prevkey] || [];
+					newkey = idx || '[]';
+				} else {
+					newobj = prev[prevkey] || {};
+					newkey = idx;
+				}
+
+				if (prevkey === '[]') {
+					prev.push(newobj);
+				} else {
+					prev[prevkey] = newobj;
+				}
+
+				prev = newobj;
+				prevkey = newkey;
+			});
+
+			if (prevkey === '[]') {
+				prev.push(val);
+			} else {
+				prev[prevkey] = val;
+			}
+		}
+
+		//if user passes simple string, we return it as an response
+		if (pairs.length === 1) {
+			return pair[0];
+		}
+
+		for( var arg = 0; arg < pairs.length; arg++ ) {
+			pair = pairs[ arg ].split( "=" );
+			injectParam(pair[0], pair[1]);
+		}
+
+		return obj;
+	}
+
+	/**
+	 * Checks if hash contains correct rpc call
+	 */
+	function isHash( str ) {
+		return /#\d{14}:rpc(.*)&/.test( str );
+	}
+
+	function extractBaseUrl( url ) {
+		return url.replace( /([^:]+:\/\/[^\/]+).*/, '$1' )
+	}
+
+	/**
+	 * Function tries to find rpc call through different communication channels ( hash or window rpcMessage property )
+	 *
+	 * @return {Object} return
+	 */
+	function catchEvent() {
+		var channel = '';
+		if( !has_postMessage && location.hash.length > 0 && /^#\d+:rpc&.*$/.test( location.hash ) ) {
+			channel = location.hash;
+			location.hash = '';
+		} else if( window.rpcMessage && window.rpcMessage.length > 0 && /^#\d+:rpc&.*$/.test( window.rpcMessage ) ) {
+			channel = window.rpcMessage;
+			window.rpcMessage = '';
+		} else {
+			return;
+		}
+
+		return toObject( channel.replace( /#\d+:rpc&/, '' ) );
+	}
+
+	function receiveMessage(data) {
+		for( var i = 0; i < receiveCallbacks.length; ++i ) {
+			var cb = receiveCallbacks[ i ].pmcb || receiveCallbacks[ i ].cb;
+			cb( { data: data } );
+		}
+	}
+
+	/**
+	 * Polling loop
+	 */
+	function startPolling() {
+		interval_id && clearInterval( interval_id );
+		interval_id = null;
+
+		interval_id = setInterval( function() {
+			var event = catchEvent(),
+				message = event && event.message;
+
+			if( event ) {
+				if (message && message.name === 'rpc.batch' &&
+					message.data && message.data.length > 0) {
+					for (var i=0; i < message.data.length; ++i ) {
+						receiveMessage(message.data[i]);
+					}
+				} else {
+					receiveMessage(event);
+				}
+			}
+		}, pollDelay );
+	}
+
+	/**
+	 * @namespace LJ.rpc
+	 */
+	window.LJ = window.LJ || {};
+	LJ.rpc = LJ.rpc || {};
+
+	/**
+	 * Send message to the other window. If senderUrl us set the code acts like child window.
+	 *
+	 * @param {Object|String} message
+	 * @param {Object} target Window object of the window to send message to
+	 */
+	LJ.rpc.postMessage = function( message, target ) {
+		target = target || parent;
+
+		var packet = createPacket(message),
+			target_url,
+			useFrame = false,
+			targetInfo = targets.getTarget(target),
+			//all this is done to prevent the usage of postMessage transport
+			//between container and popup in IE8
+			usePM = has_postMessage || (isIE8 && (
+				(targetInfo && targetInfo.isFrame) ||
+				((target === parent) && !isPopup)));
+
+		target_url = targetInfo && target.href;
+
+		if (!(targetInfo)) {
+			if(usePM) {
+				target_url = "*";
+			} else {
+				if( !senderUrl ) {
+					// throw "Transport channel has not been init for communication";
+					//all these messages will be sent as soon as sender will be set
+					unsentMessages.push(jQuery.extend(true, {}, packet));
+				} else {
+					target_url = senderUrl;
+					useFrame = true;
+				}
+			}
+		}
+
+		if (usePM) {
+			window.setTimeout( function() {
+				if (isIE8) {
+					packet = jQuery.param(packet);
+				}
+				target.postMessage(packet, extractBaseUrl( target_url ) );
+			}, 0 );
+
+		} else if ( target_url ) {
+			if( useFrame ) {
+				// window.open( iframeUrl + toHash( message ), 'xdr' );
+				$( senderDomainFrame ).attr( 'src', iframeUrl + toHash( message ) );
+			} else {
+				target.location = target_url.replace( /#.*$/, '' ) + toHash( message );
+			}
+		}
+	};
+
+	/**
+	 * Bind callback to listen messages
+	 *
+	 * @param {Function} callback
+	 */
+	LJ.rpc.bind = function( callback ) {
+		if ( has_postMessage || isIE8 ) {
+			var rm_callback = function(e) {
+				e = e || window.event;
+				if (isIE8) {
+					try {
+						e = { data: toObject(e.data) };
+					} catch(err) {};
+				}
+				callback( e );
+			};
+
+			if ( window.addEventListener ) {
+				window.addEventListener( 'message', rm_callback, false );
+			} else {
+				window.attachEvent( 'onmessage', rm_callback );
+			}
+			receiveCallbacks.push( { cb: callback, pmcb: rm_callback } );
+		} else {
+			receiveCallbacks.push( { cb: callback } );
+		}
+	};
+
+	/**
+	 * Stop listening messages with callback
+	 */
+	LJ.rpc.unbind = function( callback ) {
+		for( var i = 0; i < receiveCallbacks.length; ++i ) {
+			if( receiveCallbacks[ i ].cb === callback ) {
+				if ( has_postMessage ) {
+					if ( window.addEventListener ) {
+						window.removeEventListener( 'message', receiveCallbacks[ i ] );
+					} else {
+						window.detachEvent( 'onmessage', receiveCallbacks[ i ] );
+					}
+				}
+				receiveCallbacks.splice( i, 1 );
+				return;
+			}
+		}
+	}
+
+	/**
+	 * Init communication with child window from container. Function makes sense only for browsers
+	 *     that do not support postMessage.
+	 *
+	 *  @param {Object} w window object of child window.
+	 *  @param {String} recipientUrl url if the child window
+	 *  @param {String} senderUrl url of the container window
+	 *  @param {boolean=} isFrame with this flag equal to true user can explictly show that recipient is frame.
+	 *      In this case IE8 will use postMessage transport. Default is false
+	 */
+	LJ.rpc.initRecipient = function( w, recipientUrl, senderUrl, isFrame ) {
+		//if only fallback method is used, we have to pass parent window url with query string to enable communication
+		if( !(has_postMessage || (isIE8 && isFrame))) {
+			w.location = recipientUrl.replace( /#.*$/, '' ) + toHash( { transport_url: senderUrl } );
+		} else if (!isFrame) {
+			w.location = recipientUrl;
+		}
+
+		targets.add( w, recipientUrl, !!isFrame );
+	};
+
+	//plugin listens messages in case if parent window will send it's url.
+	if( !has_postMessage ) {
+		var bootstrap = function( e ) {
+			if( e.data && e.data.message && e.data.message.transport_url ) {
+				LJ.rpc.setSender( e.data.message.transport_url );
+
+				if (unsentMessages.length > 0) {
+					LJ.rpc.postMessage({
+						name: 'rpc.batch',
+						data: unsentMessages
+					});
+
+					unsentMessages.length = 0;
+				}
+
+				LJ.rpc.unbind( bootstrap );
+			}
+		};
+		LJ.rpc.bind( bootstrap );
+	}
+
+	/**
+	 * Set Url of the parent window in the child. Function also initiates communication channel,
+	 *    if it has not been done yet.
+	 *
+	 * @param {String} url
+	 */
+	LJ.rpc.setSender = function( url ) {
+		senderUrl = url;
+
+		//setting senderUrl means that we're in child window, so we need
+		//to create the page in the domain of parent window
+		if( !has_postMessage ) {
+			iframeUrl = extractBaseUrl( url ) + '/xdr.html';
+
+			if( !senderDomainFrame ) {
+				senderDomainFrame = document.createElement( 'iframe' );
+				senderDomainFrame.id = 'xdr';
+				senderDomainFrame.src = iframeUrl;
+				senderDomainFrame.style.display = 'none';
+
+				document.body.insertBefore( senderDomainFrame, document.body.firstChild );
+			} else {
+				$( senderDomainFrame ).attr( 'src', iframeUrl );
+			}
+
+		}
+	};
+
+	/**
+	 * Return current url of the parent window.
+	 *
+	 * @return {String}
+	 */
+	LJ.rpc.getSender = function() {
+		return senderUrl;
+	};
+
+	LJ.rpc.hasPostMessage = has_postMessage;
+
+	startPolling();
+
+})( window, jQuery );
+

Added: trunk/htdocs/js/lj.rpc.js
===================================================================
--- trunk/htdocs/js/lj.rpc.js	                        (rev 0)
+++ trunk/htdocs/js/lj.rpc.js	2012-02-09 14:06:53 UTC (rev 11460)
@@ -0,0 +1,56 @@
+/**
+ * This file represent fuctionality that livejournal exposes to the
+ * iframes from third-party domains.
+ */
+(function($) {
+	var getFrame = function(name) {
+		var frame =  $('#' + name);
+
+		return frame.length === 0 ? null : frame;
+	};
+
+	var rpc = {
+		adjustHeight: function(frameId, height) {
+			var MAX_HEIGHT = 3000;
+			if (height < 0) { return; }
+			if (height > MAX_HEIGHT) { height = MAX_HEIGHT; }
+
+			var frame = getFrame(frameId);
+
+			if (frame) {
+				frame.height(height);
+			}
+		}
+	};
+
+	var handlePostMessage = function(ev) {
+			var frameId = ev.data.frameId,
+				message = ev.data.message,
+				func;
+
+			if (frameId && message && message.name && message.name.match(/^rpc\./)) {
+				func = message.name.substr(4);
+
+				if (rpc.hasOwnProperty(func)) {
+					rpc[func].apply(null, [frameId].concat(message.data));
+				}
+			}
+		},
+		init = function() {
+			if(LJ.rpc) {
+				LJ.rpc.bind(handlePostMessage);
+			} else {
+				setTimeout(init, 10);
+			}
+		}, 
+		initFrames = function() {
+			$('iframe.rpc').each(function() {
+				LJ.rpc.initRecipient(this.contentWindow,
+							this.getAttribute('src'), location.href.replace( /#.*$/, '' ), true);
+			});
+		};
+
+	init();
+	jQuery(initFrames);
+})(jQuery);
+

Modified: trunk/htdocs/misc/xdr.html
===================================================================
--- trunk/htdocs/misc/xdr.html	2012-02-09 14:04:30 UTC (rev 11459)
+++ trunk/htdocs/misc/xdr.html	2012-02-09 14:06:53 UTC (rev 11460)
@@ -3,9 +3,10 @@
 <head>
 	<meta charset="UTF-8">
 	<script type="text/javascript">
+		var parentWin = window.parent.opener || window.parent.parent; //frame or popup
 		function sendData() {
 			if ( location.hash && location.hash.length > 1 ) {
-				window.parent.opener.rpcMessage = location.hash;
+				parentWin.rpcMessage = location.hash;
 				location.hash = '#';
 			}
 		}

Tags: can3p, dpetrov, html, js, ljcom
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments