/** * writeCapture.js v1.0.5 * * @author noah * */ (function($,global) { var doc = global.document; function doEvil(code) { var div = doc.createElement('div'); doc.body.insertBefore(div,null); $.replaceWith(div,''); } // ensure we have our support functions $ = $ || (function(jQuery) { /** * @name writeCaptureSupport * * The support functions writeCapture needs. */ return { /** * Takes an options parameter that must support the following: * { * url: url, * type: 'GET', // all requests are GET * dataType: "script", // it this is set to script, script tag injection is expected, otherwise, treat as plain text * async: true/false, // local scripts are loaded synchronously by default * success: callback(text,status), // must not pass a truthy 3rd parameter * error: callback(xhr,status,error) // must pass truthy 3rd parameter to indicate error * } */ ajax: jQuery.ajax, /** * @param {String Element} selector an Element or selector * @return {Element} the first element matching selector */ $: function(s) { return jQuery(s)[0]; }, /** * @param {String jQuery Element} selector the element to replace. * writeCapture only needs the first matched element to be replaced. * @param {String} content the content to replace * the matched element with. script tags must be evaluated/loaded * and executed if present. */ replaceWith: function(selector,content) { // jQuery 1.4? has a bug in replaceWith so we can't use it directly var el = jQuery(selector)[0]; var next = el.nextSibling, parent = el.parentNode; jQuery(el).remove(); if ( next ) { jQuery(next).before( content ); } else { jQuery(parent).append( content ); } }, onLoad: function(fn) { jQuery(fn); }, copyAttrs: function(src,dest) { var el = jQuery(dest), attrs = src.attributes; for (var i = 0, len = attrs.length; i < len; i++) { if(attrs[i] && attrs[i].value) { try { el.attr(attrs[i].name,attrs[i].value); } catch(e) { } } } } }; })(global.jQuery); $.copyAttrs = $.copyAttrs || function() {}; $.onLoad = $.onLoad || function() { throw "error: autoAsync cannot be used without jQuery " + "or defining writeCaptureSupport.onLoad"; }; // utilities function each(array,fn) { for(var i =0, len = array.length; i < len; i++) { if( fn(array[i]) === false) return; } } function isFunction(o) { return Object.prototype.toString.call(o) === "[object Function]"; } function isString(o) { return Object.prototype.toString.call(o) === "[object String]"; } function slice(array,start,end) { return Array.prototype.slice.call(array,start || 0,end || array && array.length); } function any(array,fn) { var result = false; each(array,check); function check(it) { return !(result = fn(it)); } return result; } function SubQ(parent) { this._queue = []; this._children = []; this._parent = parent; if(parent) parent._addChild(this); } SubQ.prototype = { _addChild: function(q) { this._children.push(q); }, push: function (task) { this._queue.push(task); this._bubble('_doRun'); }, pause: function() { this._bubble('_doPause'); }, resume: function() { this._bubble('_doResume'); }, _bubble: function(name) { var root = this; while(!root[name]) { root = root._parent; } return root[name](); }, _next: function() { if(any(this._children,runNext)) return true; function runNext(c) { return c._next(); } var task = this._queue.shift(); if(task) { task(); } return !!task; } }; /** * Provides a task queue for ensuring that scripts are run in order. * * The only public methods are push, pause and resume. */ function Q(parent) { if(parent) { return new SubQ(parent); } SubQ.call(this); this.paused = 0; } Q.prototype = (function() { function f() {} f.prototype = SubQ.prototype; return new f(); })(); Q.prototype._doRun = function() { if(!this.running) { this.running = true; try { // just in case there is a bug, always resume // if paused is less than 1 while(this.paused < 1 && this._next()){} } finally { this.running = false; } } }; Q.prototype._doPause= function() { this.paused++; }; Q.prototype._doResume = function() { this.paused--; this._doRun(); }; // TODO unit tests... function MockDocument() { } MockDocument.prototype = { _html: '', open: function( ) { this._opened = true; if(this._delegate) { this._delegate.open(); } }, write: function(s) { if(this._closed) return; this._written = true; if(this._delegate) { this._delegate.write(s); } else { this._html += s; } }, writeln: function(s) { this.write(s + '\n'); }, close: function( ) { this._closed = true; if(this._delegate) { this._delegate.close(); } }, copyTo: function(d) { this._delegate = d; d.foobar = true; if(this._opened) { d.open(); } if(this._written) { d.write(this._html); } if(this._closed) { d.close(); } } }; // test for IE 6/7 issue (issue 6) that prevents us from using call var canCall = (function() { var f = { f: doc.getElementById }; try { f.f.call(doc,'abc'); return true; } catch(e) { return false; } })(); function unProxy(elements) { each(elements,function(it) { var real = doc.getElementById(it.id); if(!real) { logError('', 'no element in writen markup with id ' + it.id); return; } each(it.el.childNodes,function(it) { real.appendChild(it); }); if(real.contentWindow) { // TODO why is the setTimeout necessary? global.setTimeout(function() { it.el.contentWindow.document. copyTo(real.contentWindow.document); },1); } $.copyAttrs(it.el,real); }); } function getOption(name,options) { if(options && options[name] === false) { return false; } return options && options[name] || self[name]; } function capture(context,options) { var tempEls = [], proxy = getOption('proxyGetElementById',options), forceLast = getOption('forceLastScriptTag',options), writeOnGet = getOption('writeOnGetElementById',options), immediate = getOption('immediateWrites', options), state = { write: doc.write, writeln: doc.writeln, finish: function() {}, out: '' }; context.state = state; doc.write = immediate ? immediateWrite : replacementWrite; doc.writeln = immediate ? immediateWriteln : replacementWriteln; if(proxy || writeOnGet) { state.getEl = doc.getElementById; doc.getElementById = getEl; if(writeOnGet) { findEl = writeThenGet; } else { findEl = makeTemp; state.finish = function() { unProxy(tempEls); }; } } if(forceLast) { state.getByTag = doc.getElementsByTagName; doc.getElementsByTagName = function(name) { var result = slice(canCall ? state.getByTag.call(doc,name) : state.getByTag(name)); if(name === 'script') { result.push( $.$(context.target) ); } return result; }; var f = state.finish; state.finish = function() { f(); doc.getElementsByTagName = state.getByTag; }; } function replacementWrite(s) { state.out += s; } function replacementWriteln(s) { state.out += s + '\n'; } function immediateWrite(s) { var target = $.$(context.target); var div = doc.createElement('div'); target.parentNode.insertBefore(div,target); $.replaceWith(div,sanitize(s)); } function immediateWriteln(s) { var target = $.$(context.target); var div = doc.createElement('div'); target.parentNode.insertBefore(div,target); $.replaceWith(div,sanitize(s) + '\n'); } function makeTemp(id) { var t = doc.createElement('div'); tempEls.push({id:id,el:t}); // mock contentWindow in case it's supposed to be an iframe t.contentWindow = { document: new MockDocument() }; return t; } function writeThenGet(id) { var target = $.$(context.target); var div = doc.createElement('div'); target.parentNode.insertBefore(div,target); $.replaceWith(div,state.out); state.out = ''; return canCall ? state.getEl.call(doc,id) : state.getEl(id); } function getEl(id) { var result = canCall ? state.getEl.call(doc,id) : state.getEl(id); return result || findEl(id); } return state; } function uncapture(state) { doc.write = state.write; doc.writeln = state.writeln; if(state.getEl) { doc.getElementById = state.getEl; } return state.out; } function clean(code) { // IE will execute inline scripts with