diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js index 806e8beb15..fec534de72 100644 --- a/chrome/content/zotero/xpcom/http.js +++ b/chrome/content/zotero/xpcom/http.js @@ -246,8 +246,10 @@ Zotero.HTTP = new function() { // Don't cache GET requests xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, onDone, responseCharset); }; @@ -332,8 +334,10 @@ Zotero.HTTP = new function() { xmlhttp.setRequestHeader(header, headers[header]); } + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, onDone, responseCharset); }; @@ -394,8 +398,10 @@ Zotero.HTTP = new function() { // Don't cache HEAD requests xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, onDone); }; @@ -431,8 +437,10 @@ Zotero.HTTP = new function() { xmlhttp.mozBackgroundRequest = true; xmlhttp.open('OPTIONS', uri.spec, true); + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, callback); }; xmlhttp.send(null); @@ -471,8 +479,10 @@ Zotero.HTTP = new function() { xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, function (xmlhttp) { Zotero.debug("Proxy auth request completed with status " + xmlhttp.status + ": " + xmlhttp.responseText); @@ -548,8 +558,10 @@ Zotero.HTTP = new function() { xmlhttp.setRequestHeader("Content-Type", 'text/xml; charset="utf-8"'); + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, callback); }; @@ -582,8 +594,10 @@ Zotero.HTTP = new function() { // Prevent certificate/authentication dialogs from popping up xmlhttp.mozBackgroundRequest = true; xmlhttp.open('MKCOL', uri.spec, true); + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, callback); }; xmlhttp.send(null); @@ -625,8 +639,10 @@ Zotero.HTTP = new function() { // with Content-Length: 0, which triggers a "no element found" error // in Firefox, so we override to text xmlhttp.overrideMimeType("text/plain"); + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, callback); }; xmlhttp.send(body); @@ -662,8 +678,10 @@ Zotero.HTTP = new function() { // Firefox 3 throws a "no element found" error even with a // 204 ("No Content") response, so we override to text xmlhttp.overrideMimeType("text/plain"); + var useMethodjit = Components.utils.methodjit; /** @ignore */ xmlhttp.onreadystatechange = function() { + Components.utils.methodjit = useMethodjit; _stateChange(xmlhttp, callback); }; xmlhttp.send(null); diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 6dc724552f..70c786e179 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -186,20 +186,28 @@ Components.utils.import("resource://gre/modules/Services.jsm"); // whether we are waiting for another Zotero process to release its DB lock var _waitingForDBLock = false; + /** + * Maintains nsITimers to be used when Zotero.wait() completes (to reduce performance penalty + * of initializing new objects) + */ + var _waitTimers = []; + /** * Maintains nsITimerCallbacks to be used when Zotero.wait() completes */ var _waitTimerCallbacks = []; + /** + * Maintains running nsITimers in global scope, so that they don't disappear randomly + */ + var _runningTimers = []; + // Errors that were in the console at startup var _startupErrors = []; // Number of errors to maintain in the recent errors buffer const ERROR_BUFFER_SIZE = 25; // A rolling buffer of the last ERROR_BUFFER_SIZE errors var _recentErrors = []; - - // The hidden DOM window - var _hiddenDOMWindow; /** * Initialize the extension @@ -239,12 +247,14 @@ Components.utils.import("resource://gre/modules/Services.jsm"); } // OS platform - _hiddenDOMWindow = Services.appShell.hiddenDOMWindow; - this.platform = _hiddenDOMWindow.navigator.platform; + var win = Components.classes["@mozilla.org/appshell/appShellService;1"] + .getService(Components.interfaces.nsIAppShellService) + .hiddenDOMWindow; + this.platform = win.navigator.platform; this.isMac = (this.platform.substr(0, 3) == "Mac"); this.isWin = (this.platform.substr(0, 3) == "Win"); this.isLinux = (this.platform.substr(0, 5) == "Linux"); - this.oscpu = _hiddenDOMWindow.navigator.oscpu; + this.oscpu = win.navigator.oscpu; // Browser Zotero.browser = "g"; @@ -1511,9 +1521,10 @@ Components.utils.import("resource://gre/modules/Services.jsm"); _waiting--; // requeue nsITimerCallbacks that came up during Zotero.wait() but couldn't execute - for(var i=0; i<_waitTimerCallbacks.length; i++) { - Zotero.setTimeout(_waitTimerCallbacks[i], 0); + for(var i in _waitTimers) { + _waitTimers[i].initWithCallback(_waitTimerCallbacks[i], 0, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } + _waitTimers = []; _waitTimerCallbacks = []; //Zotero.debug("Waited " + cycles + " cycles"); @@ -1529,8 +1540,13 @@ Components.utils.import("resource://gre/modules/Services.jsm"); this.pumpGenerator = function(generator, ms, errorHandler, doneHandler) { _waiting++; - var yielded; - var interval = _hiddenDOMWindow.setInterval(function() { + var timer = Components.classes["@mozilla.org/timer;1"]. + createInstance(Components.interfaces.nsITimer), + yielded, + useJIT = Components.utils.methodjit; + var timerCallback = {"notify":function() { + Components.utils.methodjit = useJIT; + var err = false; _waiting--; try { @@ -1544,12 +1560,14 @@ Components.utils.import("resource://gre/modules/Services.jsm"); err = e; } - _hiddenDOMWindow.clearInterval(interval); + timer.cancel(); + _runningTimers.splice(_runningTimers.indexOf(timer), 1); // requeue nsITimerCallbacks that came up during generator pumping but couldn't execute - for(var i=0; i<_waitTimerCallbacks.length; i++) { - Zotero.setTimeout(_waitTimerCallbacks[i], 0); + for(var i in _waitTimers) { + _waitTimers[i].initWithCallback(_waitTimerCallbacks[i], 0, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } + _waitTimers = []; _waitTimerCallbacks = []; if(err) { @@ -1561,7 +1579,10 @@ Components.utils.import("resource://gre/modules/Services.jsm"); } else if(doneHandler) { doneHandler(yielded); } - }, 0); + }} + timer.initWithCallback(timerCallback, ms ? ms : 0, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); + // add timer to global scope so that it doesn't get garbage collected before it completes + _runningTimers.push(timer); }; /** @@ -1585,17 +1606,27 @@ Components.utils.import("resource://gre/modules/Services.jsm"); * is executing */ this.setTimeout = function(func, ms, runWhenWaiting) { - var timerCallback = function() { + var timer = Components.classes["@mozilla.org/timer;1"]. + createInstance(Components.interfaces.nsITimer), + useJIT = Components.utils.methodjit; + var timerCallback = {"notify":function() { + Components.utils.methodjit = useJIT; + if(_waiting && !runWhenWaiting) { // if our callback gets called during Zotero.wait(), queue it to be set again // when Zotero.wait() completes + _waitTimers.push(timer); _waitTimerCallbacks.push(timerCallback); } else { // execute callback function func(); + // remove timer from global scope, so it can be garbage collected + _runningTimers.splice(_runningTimers.indexOf(timer), 1); } - }; - _hiddenDOMWindow.setTimeout(timerCallback, ms); + }} + timer.initWithCallback(timerCallback, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + // add timer to global scope so that it doesn't get garbage collected before it completes + _runningTimers.push(timer); } /** diff --git a/resource/q.js b/resource/q.js index b2b94d1325..029b5beddc 100644 --- a/resource/q.js +++ b/resource/q.js @@ -58,11 +58,49 @@ // Mozilla JSM } else if (~String(this).indexOf('BackstagePass')) { EXPORTED_SYMBOLS = ["Q"]; - Components.utils.import("resource://gre/modules/Services.jsm"); - var hiddenDOMWindow = Services.appShell.hiddenDOMWindow; // Q expects an implementation of setTimeout - setTimeout = hiddenDOMWindow.setTimeout; + setTimeout = new function() { + // We need to maintain references to running nsITimers. Otherwise, they can + // get garbage collected before they fire. + var _runningTimers = []; + + return function setTimeout(func, ms) { + var useMethodjit = Components.utils.methodjit, + timer = Components.classes["@mozilla.org/timer;1"]. + createInstance(Components.interfaces.nsITimer); + timer.initWithCallback({"notify":function() { + Components.utils.methodjit = useMethodjit; + + // Remove timer from array so it can be garbage collected + _runningTimers.splice(_runningTimers.indexOf(timer), 1); + + // Execute callback function + try { + func(); + } catch(err) { + // Rethrow errors that occur so that they appear in the error + // console with the appropriate name and line numbers. While the + // the errors appear without this, the line numbers get eaten. + var scriptError = Components.classes["@mozilla.org/scripterror;1"] + .createInstance(Components.interfaces.nsIScriptError); + scriptError.init( + err.message || err.toString(), + err.fileName || err.filename || null, + null, + err.lineNumber || null, + null, + scriptError.errorFlag, + 'component javascript' + ); + Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService) + .logMessage(scriptError); + } + }}, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + _runningTimers.push(timer); + } + }; Q = definition(); //