0b9a4a802e90edc25f19f5ec28bd03aede83c2ae
tdreszer
  Mon Oct 7 10:52:10 2013 -0700
Fix for regex which scrapes out json objects as per redmine 11873.  Some IPSs are stripping newlines so regex must tolerate that.  This problem was found by Rachel working from England.
diff --git src/hg/js/ajax.js src/hg/js/ajax.js
index a2a2d7c..2a5cb9a 100644
--- src/hg/js/ajax.js
+++ src/hg/js/ajax.js
@@ -1,512 +1,515 @@
 // AJAX Utilities
 
 var debug = false;
 
 var req;
 
 function nullProcessReqChange()
 {
     if(debug && this.readyState == 4)
             alert("req.responseText: " + req.responseText);
 }
 
 // The ajaxWaitCount code is currently NOT used, but we are keeping it in case in the future we decide we
 // really need to support sequential AJAX calls (without just using synchronous AJAX calls).
 // 
 // When setting vars with ajax, it may be necessary to wait for response before newer calls
 // Therefore this counter is set up to allow an "outside callback" when the ajax is done
 // The typical scenario is setCartVar by ajax, followed immmediately by a form submit.
 // To avoid the race condition, the form gets submitted after the ajax returns
 
 var ajaxWaitCount = 0;
 function ajaxWaitIsDone()     { return ( ajaxWaitCount <= 0 ); }
 function ajaxWaitCountUp()    { ajaxWaitCount++; }
 function ajaxWaitCountReset() { ajaxWaitCount = 0; }
 //function ajaxWaitCountShow()  { warn("ajaxWait calls outstanding:"+ajaxWaitCount); }
 
 // Here is where the "outside callback" gets set up and called
 var ajaxWaitCallbackFunction = null;
 var ajaxWaitCallbackTimeOut = null;
 function ajaxWaitCallbackRegister(func)
 { // register a function to be called when the ajax waiting is done.
     if(ajaxWaitIsDone())
         func();
     else {
         ajaxWaitCallbackFunction = func;
         ajaxWaitCallbackTimeOut = setTimeout("ajaxWaitCallback();",5000);  // just in case
     }
 }
 
 function ajaxWaitCallback()
 { // Perform the actual function (which could have been because of a callback or a timeout)
     // Clear the timeout if this is not due to ajaxWaitDone
     //warn("ajaxWaitCallback: "+ajaxWaitIsDone());
     if(ajaxWaitCallbackTimeOut != null) {
         clearTimeout(ajaxWaitCallbackTimeOut);
         ajaxWaitCallbackTimeOut = null;
     }
     // Clear the wait stack incase something failed and we are called by a timeout
     ajaxWaitCountReset();
 
     // Finally do the function
     if(ajaxWaitCallbackFunction
     && jQuery.isFunction(ajaxWaitCallbackFunction)) {
         ajaxWaitCallbackFunction();
     }
     ajaxWaitCallbackFunction = null;
 }
 
 function ajaxWaitCountDown()
 { // called whenever an ajax request is done
     if((req && req.readyState == 4)
     || (this.readyState == 4))
     { // It is only state 4 that means done
         ajaxWaitCount--;
         if(ajaxWaitIsDone())
             ajaxWaitCallback();
     }
     //warn(req.readyState + " waiters:"+ajaxWaitCount);
 }
 
 // UNUSED but useful ?
 // var formToSubmit = null; // multistate: null, {form}, "COMPLETE", "ONCEONLY"
 // function formSubmit()
 // { // This will be called as a callback on timeout or ajaxWaitCallback
 //
 //     if(formToSubmit != null) {
 //         //warn("submitting form:"+$(formToSubmit).attr('name') + ": "+ajaxWaitIsDone());
 //         var form = formToSubmit;
 //         formToSubmit = "GO"; // Flag to wait no longer
 //         $(form).submit();
 //     }
 //     waitMaskClear(); // clear any outstanding waitMask.  overkill if the form has just been submitted
 // }
 // function formSubmitRegister(form)
 // { // Registers the form submit to be done by ajaxWaitCallback or timeout
 //     if(formToSubmit != null) // Repeated submission got through, so ignore it
 //         return false;
 //     waitMaskSetup(5000);     // Will prevent repeated submissions, I hope.
 //     formToSubmit = form;
 //     //warn("Registering form to submit:"+$(form).attr('name'));
 //     ajaxWaitCallbackRegister(formSubmit);
 //     return false; // Don't submit until ajax is done.
 // }
 //
 // function formSubmitWaiter(e)
 // { // Here we will wait for up to 5 seconds before continuing.
 //     if(formToSubmit == null)
 //         return formSubmitRegister(e.target); // register on first time through
 //
 //     if(formToSubmit == "GO") {  // Called again as complete
 //         //warn("formSubmitWaiter(): GO");
 //         formToSubmit = "STOP";  // Do this only once!
 //         return true;
 //     }
 //     return false;
 // }
 //
 // function formSubmitWaitOnAjax(form)
 // { // Most typically, we block a form submit until all ajax has returned
 //     $(form).unbind('submit', formSubmitWaiter ); // prevents multiple bind requests
 //     $(form).bind(  'submit', formSubmitWaiter );
 // }
 
 function loadXMLDoc(url)
 {
 // Load XML without a request handler; this is useful if you are sending one-way messages.
         loadXMLDoc(url, null);
 }
 
 function loadXMLDoc(url, callBack)
 {
 // From http://developer.apple.com/internet/webcontent/xmlhttpreq.html
     //warn("AJAX started: "+url);
     if(callBack == null)
         callBack = nullProcessReqChange;
     req = false;
     // branch for native XMLHttpRequest object
     if(window.XMLHttpRequest && !(window.ActiveXObject)) {
         try {
             req = new XMLHttpRequest();
         } catch(e) {
             req = false;
         }
     } else if(window.ActiveXObject) {
         // branch for IE/Windows ActiveX version
         try {
             req = new ActiveXObject("Msxml2.XMLHTTP");
         } catch(e) {
             try {
                 req = new ActiveXObject("Microsoft.XMLHTTP");
             } catch(e) {
                 req = false;
             }
         }
     }
     if(debug)
         alert(url);
     if(req) {
         req.onreadystatechange = callBack;
         req.open("GET", url, true);
         req.send();
         //req.send("");
     }
 }
 
 function setCartVars(names, values, errFunc, async)
 {
 // Asynchronously sets the array of cart vars with values
     if(names.length <= 0)
         return;
 
     if (errFunc == null)
 	errFunc = errorHandler;
     if (async == null)
 	async = true;
 
     // Set up constant portion of url
     var loc = window.location.href;
     if(loc.indexOf("?") > -1) {
         loc = loc.substring(0, loc.indexOf("?"));
     }
     if(loc.lastIndexOf("/") > -1) {
         loc = loc.substring(0, loc.lastIndexOf("/"));
     }
     loc = loc + "/cartDump";
     var hgsid = getHgsid();
     var data = "submit=1&noDisplay=1&hgsid=" + hgsid;
     var track = getTrack();
     if(track && track.length > 0)
         data = data + "&g=" + track;
     for(var ix=0; ix<names.length; ix++) {
         data = data + "&" + encodeURIComponent(names[ix]) + "=" + encodeURIComponent(values[ix]);
     }
     var type;
     // We prefer GETs so we can analyze logs, but use POSTs if data is longer than a (conservatively small) 
     // maximum length to avoid problems on older versions of IE.
     if((loc.length + data.length) > 2000) {
         type = "POST";
     } else {
         type = "GET";
     }
     $.ajax({
                type: type,
                async: async,
                url: loc,
                data: data,
                trueSuccess: function () {},
                success: catchErrorOrDispatch,
                error: errFunc,
                cache: false
            });
 }
 
 function setCartVar(name, value, errFunc, async)
 {
 // Asynchronously set a cart variable.
     setCartVars( [ name ], [ value ], errFunc, async );
 }
 
 function setVarsFromHash(varHash)
 {
 // Set all vars in a var hash
 // If obj is undefined then obj is document!
     var names = [];
     var values = [];
     for (var aVar in varHash) {
         names.push(aVar);
         values.push(varHash[aVar]);
     }
     if(names.length > 0) {
         setCartVars(names,values);
     }
 }
 
 function setAllVars(obj,subtrackName)
 {
 // Set all enabled inputs and selects found as children obj with names to cart with ajax
 // If obj is undefined then obj is document!
     var names = [];
     var values = [];
     if($(obj) == undefined)
         obj = $('document');
 
     setVarsFromHash(getAllVars(obj,subtrackName));
 }
 
 // Unused but useful
 // function setCartVarFromObjId(obj)
 // {
 //     setCartVar($(obj).attr('id'),$(obj).val());
 // }
 
 function submitMain()
 {
     $('form[name="mainForm"]').submit();
 }
 
 function setCartVarAndRefresh(name,val)
 {
     setCartVar(name,val);
     var main=$('form[name="mainForm"]')
     $(main).attr('action',window.location.href);
     setTimeout("submitMain()",50);  // Delay in submit helps ensure that cart var has gotten there first.
 
     return false;
 }
 
 function errorHandler(request, textStatus)
 {
     var str;
     var tryAgain = true;
     if(textStatus && textStatus.length && textStatus != "error") {
         str = "Encountered network error : '" + textStatus + "'.";
     } else {
         if(request.responseText) {
             tryAgain = false;
             str = "Encountered error: '" + request.responseText + "'";
         } else {
             str = "Encountered a network error."
         }
     }
     if(tryAgain)
         str += " Please try again. If the problem persists, please check your network connection.";
     warn(str);
     jQuery('body').css('cursor', '');
     if(this.disabledEle) {
         this.disabledEle.removeAttr('disabled');
     }
     if(this.loadingId) {
 	hideLoadingImage(this.loadingId);
     }
 }
 
 function catchErrorOrDispatch(obj, textStatus)
 {
 // generic ajax success handler (handles fact that success is not always success).
     if(textStatus == 'success')
         this.trueSuccess(obj, textStatus);
     else
         errorHandler.call(this, obj, textStatus);
 }
 
 // Specific calls...
 function lookupMetadata(trackName,showLonglabel,showShortLabel)
 { // Ajax call to repopulate a metadata vals select when mdb var changes
     var thisData = "db=" + getDb() +  "&cmd=tableMetadata&track=" + trackName;
     if(showLonglabel)
         thisData += "&showLonglabel=1";
     if(showShortLabel)
         thisData += "&showShortLabel=1";
     $.ajax({
         type: "GET",
         dataType: "html",
         url: "../cgi-bin/hgApi",
         data: thisData,
         trueSuccess: loadMetadataTable,
         success: catchErrorOrDispatch,
         cache: true,
         cmd: trackName
     });
 }
 
 function loadMetadataTable(response, status)
 // Handle ajax response (repopulate a metadata val select)
 {
     var div = $("div#div_"+this.cmd+"_meta");
     var td = $(div).parent('td');
     if(td.length > 0 && $(td[0]).width() > 200) {
         $(td[0]).css('maxWidth',$(td[0]).width() + "px");
         $(div).css('overflow','visible');
     }
     $(div).html(response);
     if(td.length > 0 && $(td[0]).width() > 200) {
         tr = $(td[0]).parent('tr');
         if (tr.length > 0) {
             if ($(tr).hasClass("bgLevel2"))
                 $(div).children('table').addClass('bgLevel2');
             if ($(tr).hasClass("bgLevel3"))
                 $(div).children('table').addClass('bgLevel3');
         }
     }
     $(div).show();
 }
 
 function removeHgsid(href)
 {
 // remove session id from url parameters
     if(href.indexOf("?hgsid=") == -1) {
         href = href.replace(/\&hgsid=\d+/, "");
     } else {
         href = href.replace(/\?hgsid=\d+\&/, "?");
     }
     return href;
 }
 
 
 /////////////////////////
 // Retrieve extra html from a file
 var gAppendTo = null;
 
 function _retrieveHtml(fileName,obj)
 { // popup cfg dialog
     if (obj && obj != undefined && $(obj).length == 1) {
         gAppendTo = obj;
     } else
         gAppendTo = null;
 
     $.ajax({
                 type: "GET",
                 url: fileName,
                 dataType: "html",
                 trueSuccess: retrieveHtmlHandle,
                 success: catchErrorOrDispatch,
                 error: errorHandler,
                 cmd: "info",
                 cache: true
             });
 }
 
 function retrieveHtml(fileName,obj,toggle)
 { // Retrieves html from file
   // If no obj is supplied then html will be shown in a popup.
   // if obj supplied then object's html will be replaced by what is retrieved.
   //    if obj and toggle == true, and obj has html, it will be emptied, resulting in toggle like calls
     if (toggle && obj && obj != undefined && $(obj).length == 1) {
         if ($(obj).html().length > 0) {
             $(obj).html("")
             return;
         }
     }
 
     waitOnFunction( _retrieveHtml, fileName, obj );  // Launches the popup but shields the ajax with a waitOnFunction
 }
 
 function retrieveHtmlHandle(response, status)
 {
 // Take html from hgTrackUi and put it up as a modal dialog.
 
     // make sure all links (e.g. help links) open up in a new window
     response = response.replace(/<a /ig, "<a target='_blank' ");
 
     // TODO: Shlurp up any javascript files from the response and load them with $.getScript()
     // example <script type='text/javascript' SRC='../js/tdreszer/jquery.contextmenu-1296177766.js'></script>
     var cleanHtml = response;
     var shlurpPattern=/\<script type=\'text\/javascript\' SRC\=\'.*\'\>\<\/script\>/gi;
     var jsFiles = cleanHtml.match(shlurpPattern);
     cleanHtml = cleanHtml.replace(shlurpPattern,"");
     shlurpPattern=/\<script type=\'text\/javascript\'>.*\<\/script\>/gi;
     var jsEmbeded = cleanHtml.match(shlurpPattern);
     cleanHtml = cleanHtml.replace(shlurpPattern,"");
     //<LINK rel='STYLESHEET' href='../style/ui.dropdownchecklist-1276528376.css' TYPE='text/css' />
     shlurpPattern=/\<LINK rel=\'STYLESHEET\' href\=\'.*\' TYPE=\'text\/css\' \/\>/gi;
     var cssFiles = cleanHtml.match(shlurpPattern);
     cleanHtml = cleanHtml.replace(shlurpPattern,"");
 
     if (gAppendTo && gAppendTo != undefined) {
         //warn($(gAppendTo).html());
         //return;
         $(gAppendTo).html(cleanHtml);
         return;
     }
 
     var popUp = $('#popupDialog');
     if (popUp == undefined || $(popUp).length == 0) {
         $('body').prepend("<div id='popupDialog' style='display: none'></div>");
         popUp = $('#popupDialog');
     }
 
     $(popUp).html("<div id='pop'>" + cleanHtml + "</div>");
 
     $(popUp).dialog({
         ajaxOptions: { cache: true },  // This doesn't work
         resizable: true,
         height: 'auto',
         width: 'auto',
         minHeight: 200,
         minWidth: 700,
         modal: true,
         closeOnEscape: true,
         autoOpen: false,
         buttons: { "OK": function() {
             $(this).dialog("close");
         }},
         close: function() {
             var popUpClose = $('#popupDialog');
             if (popUpClose != undefined && $(popUpClose).length == 1) {
                 $(popUpClose).html("");  // clear out html after close to prevent problems caused by duplicate html elements
             }
         }
     });
 
     var myWidth =  $(window).width() - 300;
     if(myWidth > 900)
         myWidth = 900;
     $(popUp).dialog("option", "maxWidth", myWidth);
     $(popUp).dialog("option", "width", myWidth);
     $(popUp).dialog('open');
     var buttOk = $('button.ui-state-default');
     if($(buttOk).length == 1)
         $(buttOk).focus();
 }
 
 function scrapeVariable(html, name)
 {
 // scrape a variable defintion out of html (see jsHelper.c::jsPrintHash)
-    var re = new RegExp("^// START " + name + "\\nvar " + name + " = ([\\S\\s]+);\\n// END " + name + "$", "m");
+    // NOTE: newlines are being removed by some ISPs, so regex must tolerate that:
+    var re = new RegExp("// START " + name + 
+                        "\\n?var " + name + " = ([\\S\\s]+);" + 
+                        "\\n?// END " + name, "m");
     var a = re.exec(html);
     var json;
     if(a && a[1]) {
         json = eval("(" + a[1] + ")");
     }
     return json;
 }
 
 // The loadingImage module helps you manage a loading image (for a slow upload; e.g. in hgCustom).
 
 var loadingImage = function ()
 {
     // private vars
     var imgEle, msgEle, statusMsg;
 
     // private methods
     var refreshLoadingImg = function()
     {
         // hack to make sure animation continues in IE after form submission
         // See: http://stackoverflow.com/questions/774515/keep-an-animated-gif-going-after-form-submits
         // and http://stackoverflow.com/questions/780560/animated-gif-in-ie-stopping
         imgEle.attr('src', imgEle.attr('src'));
     };
 
     // public methods
     return {
         init: function(_imgEle, _msgEle, _msg)
         {
             // This should be called from the ready method; imgEle and msgEle should be jQuery objects
             imgEle = _imgEle;
             msgEle = _msgEle;
             statusMsg = _msg;
             // To make the loadingImg visible on FF, we have to make sure it's visible during page load (i.e. in html) otherwise it doesn't get shown by the submitClick code.
             imgEle.hide();
         },
         run: function() {
             msgEle.append(statusMsg);
             if(navigator.userAgent.indexOf("Chrome") != -1) {
                 // In Chrome, gif animation and setTimeout's are stopped when the browser receives the first blank line/comment of the next page
                 // (basically, the current page is unloaded). I have found no way around this problem, so we just show a 
                 // simple "Processing..." message (we can't make that blink, b/c Chrome doesn't support blinking text).
                 // 
                 // (Surprisingly, this is NOT true for Safari, so this is apparently not a WebKit issue).
                 imgEle.replaceWith("<span id='loadingBlinker'>&nbsp;&nbsp;<b>Processing...</b></span>");
             } else {
                 imgEle.show();
                 setTimeout(refreshLoadingImg, 1000);
             }
         },
         abort: function() {
 	    imgEle.hide();
 	    msgEle.hide();
 	    jQuery('body').css('cursor', '');
 	}
     }
 }();