diff --git a/chrome/content/zotero/debugViewer.js b/chrome/content/zotero/debugViewer.js
index b9fb8b6339d..f85796f2988 100644
--- a/chrome/content/zotero/debugViewer.js
+++ b/chrome/content/zotero/debugViewer.js
@@ -38,7 +38,7 @@ function updateErrors() {
.then(function (sysInfo) {
if (stopping) return;
- var errors = Zotero.getErrors(true);
+ var errors = Zotero.Errors.getErrors(true);
var errorStr = errors.length ? errors.join('\n\n') + '\n\n' : '';
document.getElementById('errors').textContent = errorStr + sysInfo;
diff --git a/chrome/content/zotero/errorReport.xul b/chrome/content/zotero/errorReport.xul
index b4214dd92ce..b06929b3ec6 100644
--- a/chrome/content/zotero/errorReport.xul
+++ b/chrome/content/zotero/errorReport.xul
@@ -18,8 +18,6 @@
var Zotero = obj.Zotero;
var data = obj.data;
var msg = data.msg;
- var errorData = data.errorData;
- var extraData = data.extraData ? data.extraData : '';
var diagnosticInfo = false;
@@ -29,13 +27,12 @@
continueButton.disabled = true;
diagnosticInfo = yield Zotero.getSystemInfo();
+ var errorData = Zotero.Errors.getErrors();
var errorDataText = errorData.length
- ? data.errorData.join('\n\n')
+ ? errorData.join('\n\n')
: Zotero.getString('errorReport.noErrorsLogged', Zotero.appName);
- var logText = errorDataText + '\n\n'
- + (extraData !== '' ? extraData + '\n\n' : '')
- + diagnosticInfo;
+ var logText = diagnosticInfo + '\n\n' + errorDataText;
if (document.getElementById('zotero-failure-message').hasChildNodes()) {
var textNode = document.getElementById('zotero-failure-message').firstChild;
@@ -57,89 +54,22 @@
var wizard = document.getElementById('zotero-error-report');
var continueButton = wizard.getButton('next');
continueButton.disabled = true;
-
- var parts = {
- error: "true",
- errorData: errorData.join('\n'),
- extraData: extraData,
- diagnostic: diagnosticInfo
- };
-
- var body = '';
- for (var key in parts) {
- body += key + '=' + encodeURIComponent(parts[key]) + '&';
- }
- body = body.substr(0, body.length - 1);
- var req = yield Zotero.HTTP.request(
- "POST",
- ZOTERO_CONFIG.REPOSITORY_URL + "report",
- {
- body,
- successCodes: false,
- foreground: true
- }
- );
- _sendErrorReportCallback(req);
- });
-
- function _sendErrorReportCallback(xmlhttp) {
- var wizard = document.getElementById('zotero-error-report');
- if (!wizard) {
- return;
- }
-
- var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
-
- if (!xmlhttp.responseXML){
- try {
- if (xmlhttp.status>1000){
- ps.alert(
- null,
- Zotero.getString('general.error'),
- Zotero.getString('errorReport.noNetworkConnection')
- );
- }
- else {
- ps.alert(
- null,
- Zotero.getString('general.error'),
- Zotero.getString('errorReport.invalidResponseRepository')
- );
- }
- }
- catch (e){
- ps.alert(
- null,
- Zotero.getString('general.error'),
- Zotero.getString('errorReport.repoCannotBeContacted')
- );
- }
-
- wizard.rewind();
- return;
- }
-
-
- var reported = xmlhttp.responseXML.getElementsByTagName('reported');
- if (reported.length != 1) {
- ps.alert(
- null,
- Zotero.getString('general.error'),
- Zotero.getString('errorReport.invalidResponseRepository')
- );
+ var reportID;
+ try {
+ reportID = yield Zotero.Errors.submitToZotero();
+ } catch (e) {
+ ps.alert(null, Zotero.getString('general.error'), e.message);
wizard.rewind();
return;
}
wizard.advance();
- wizard.getButton('cancel').disabled = true;;
+ wizard.getButton('cancel').disabled = true;
wizard.canRewind = false;
- var reportID = reported[0].getAttribute('reportID');
document.getElementById('zotero-report-id').setAttribute('value', reportID);
document.getElementById('zotero-report-result').hidden = false;
- }
+ });
}
]]>
diff --git a/chrome/content/zotero/standalone/standalone.js b/chrome/content/zotero/standalone/standalone.js
index aa7f9201495..27e4026157c 100644
--- a/chrome/content/zotero/standalone/standalone.js
+++ b/chrome/content/zotero/standalone/standalone.js
@@ -240,7 +240,7 @@ ZoteroStandalone.DebugOutput = {
toggleStore: function () {
- Zotero.Debug.setStore(!Zotero.Debug.storing);
+ Zotero.Debug.setStore(!Zotero.Debug.storing, true);
},
@@ -268,96 +268,21 @@ ZoteroStandalone.DebugOutput = {
submit: function () {
- // 'Zotero' isn't defined yet when this function is created, so do it inline
return Zotero.Promise.coroutine(function* () {
- Components.utils.import("resource://zotero/config.js");
-
- var url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1";
- var output = yield Zotero.Debug.get(
- Zotero.Prefs.get('debug.store.submitSize'),
- Zotero.Prefs.get('debug.store.submitLineLength')
- );
- Zotero.Debug.setStore(false);
-
var ps = Services.prompt;
try {
- var xmlhttp = yield Zotero.HTTP.request(
- "POST",
- url,
- {
- compressBody: true,
- body: output,
- logBodyLength: 30,
- timeout: 15000,
- requestObserver: function (req) {
- // Don't fail during tests, with fake XHR
- if (!req.channel) {
- return;
- }
- req.channel.notificationCallbacks = {
- onProgress: function (request, context, progress, progressMax) {},
-
- // nsIInterfaceRequestor
- getInterface: function (iid) {
- try {
- return this.QueryInterface(iid);
- }
- catch (e) {
- throw Components.results.NS_NOINTERFACE;
- }
- },
-
- QueryInterface: function(iid) {
- if (iid.equals(Components.interfaces.nsISupports) ||
- iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
- iid.equals(Components.interfaces.nsIProgressEventSink)) {
- return this;
- }
- throw Components.results.NS_NOINTERFACE;
- },
-
- }
- }
- }
- );
- }
- catch (e) {
- Zotero.logError(e);
- let title = Zotero.getString('general.error');
- let msg;
- if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
- msg = Zotero.getString('general.invalidResponseServer');
- }
- else if (e instanceof Zotero.HTTP.BrowserOfflineException) {
- msg = Zotero.getString('general.browserIsOffline', Zotero.appName);
- }
- else {
- msg = Zotero.getString('zotero.debugOutputLogging.dialog.error');
- }
- ps.alert(null, title, msg);
+ var debugID = yield Zotero.Debug.submitToZotero();
+ } catch (e) {
+ ps.alert(null, Zotero.getString('general.error'), e.message);
return false;
}
- Zotero.debug(xmlhttp.responseText);
-
- var reported = xmlhttp.responseXML.getElementsByTagName('reported');
- if (reported.length != 1) {
- ps.alert(
- null,
- Zotero.getString('general.error'),
- Zotero.getString('general.serverError')
- );
- return false;
- }
-
- var reportID = reported[0].getAttribute('reportID');
-
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
var index = ps.confirmEx(
null,
Zotero.getString('zotero.debugOutputLogging.dialog.title'),
- Zotero.getString('zotero.debugOutputLogging.dialog.sent', [ZOTERO_CONFIG.DOMAIN_NAME, reportID]),
+ Zotero.getString('zotero.debugOutputLogging.dialog.sent', [ZOTERO_CONFIG.DOMAIN_NAME, debugID.substr(1)]),
buttonFlags,
Zotero.getString('general.copyToClipboard'),
null, null, null, {}
@@ -365,12 +290,10 @@ ZoteroStandalone.DebugOutput = {
if (index == 0) {
const helper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
.getService(Components.interfaces.nsIClipboardHelper);
- helper.copyString("D" + reportID);
+ helper.copyString(debugID);
}
-
- Zotero.Debug.clear();
return true;
- }.bind(this))();
+ }).apply(this, arguments);
},
@@ -381,13 +304,13 @@ ZoteroStandalone.DebugOutput = {
submitted = true;
});
doc.querySelector('#clear-button').addEventListener('click', function (event) {
- Zotero.Debug.clear();
+ Zotero.Debug.clear(true);
});
// If output has been submitted, disable logging when window is closed
doc.defaultView.addEventListener('unload', function (event) {
if (submitted) {
- Zotero.Debug.setStore(false);
- Zotero.Debug.clear();
+ Zotero.Debug.setStore(false, true);
+ Zotero.Debug.clear(true);
}
});
});
@@ -395,7 +318,7 @@ ZoteroStandalone.DebugOutput = {
clear: function () {
- Zotero.Debug.clear();
+ Zotero.Debug.clear(true);
},
diff --git a/chrome/content/zotero/xpcom/connector/server_connector.js b/chrome/content/zotero/xpcom/connector/server_connector.js
index 409d216e71a..9723bdbe554 100644
--- a/chrome/content/zotero/xpcom/connector/server_connector.js
+++ b/chrome/content/zotero/xpcom/connector/server_connector.js
@@ -112,6 +112,47 @@ Zotero.Server.Connector.AttachmentProgressManager = new function() {
}
};
+
+Zotero.Server.Connector.Reports = function() {};
+Zotero.Server.Connector.Reports.reports = [];
+Zotero.Server.Endpoints["/connector/reports"] = Zotero.Server.Connector.Reports;
+Zotero.Server.Connector.Reports.prototype = {
+ supportedMethods: ["POST"],
+ supportedDataTypes: ["application/json"],
+ permitBookmarklet: true,
+
+ /**
+ * An endpoint to manage error/debug logging and reporting
+ */
+ init: async function(options) {
+ let data = options.data;
+ if ('errors' in data && 'get' in data.errors) {
+ let sysInfo = await Zotero.getSystemInfo();
+ let errors = Zotero.Errors.getErrors();
+ return [200, "text/plain", `${sysInfo}\n\n${errors.join('\n\n')}`]
+ }
+ else if ('debug' in data) {
+ if ('get' in data.debug) {
+ let debug = await Zotero.Debug.get();
+ return [200, "text/plain", debug]
+ }
+ else if ('store' in data.debug) {
+ Zotero.Debug.setStore(data.debug.store, false);
+ }
+ else if ('clear' in data.debug) {
+ Zotero.Debug.clear(false);
+ }
+ return 200;
+ } else if ('report' in data) {
+ Zotero.Server.Connector.Reports.reports.push(data.report);
+ return 200;
+ }
+
+ return 400;
+ }
+};
+
+
/**
* Lists all available translators, including code for translators that should be run on every page
*
diff --git a/chrome/content/zotero/xpcom/debug.js b/chrome/content/zotero/xpcom/debug.js
deleted file mode 100644
index 0e5468f591d..00000000000
--- a/chrome/content/zotero/xpcom/debug.js
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-
-Zotero.Debug = new function () {
- var _console, _stackTrace, _store, _level, _lastTime, _output = [];
- var _slowTime = false;
- var _colorOutput = false;
- var _consoleViewer = false;
- var _consoleViewerQueue = [];
- var _consoleViewerListener;
-
- /**
- * Initialize debug logging
- *
- * Debug logging can be set in several different ways:
- *
- * - via the debug.log pref in the client or connector
- * - by enabling debug output logging from the Help menu
- * - by passing -ZoteroDebug or -ZoteroDebugText on the command line
- *
- * In the client, debug.log and -ZoteroDebugText enable logging via the terminal, while -ZoteroDebug
- * enables logging via an in-app HTML-based window.
- *
- * @param {Integer} [forceDebugLog = 0] - Force output even if pref disabled
- * 2: window (-ZoteroDebug)
- * 1: text console (-ZoteroDebugText)
- * 0: disabled
- */
- this.init = function (forceDebugLog = 0) {
- _console = Zotero.Prefs.get('debug.log') || forceDebugLog == 1;
- _consoleViewer = forceDebugLog == 2;
- // When logging to the text console from the client on Mac/Linux, colorize output
- if (_console && Zotero.isFx && !Zotero.isBookmarklet) {
- _colorOutput = true;
-
- // Time threshold in ms above which intervals should be colored red in terminal output
- _slowTime = Zotero.Prefs.get('debug.log.slowTime');
- }
- _store = Zotero.Prefs.get('debug.store');
- if (_store) {
- Zotero.Prefs.set('debug.store', false);
- }
- _level = Zotero.Prefs.get('debug.level');
- _stackTrace = Zotero.Prefs.get('debug.stackTrace');
-
- this.storing = _store;
- this.updateEnabled();
-
- if (Zotero.isStandalone) {
- // Enable dump() from window (non-XPCOM) scopes when terminal or viewer logging is enabled.
- // (These will always go to the terminal, even in viewer mode.)
- Zotero.Prefs.set('browser.dom.window.dump.enabled', _console || _consoleViewer, true);
-
- if (_consoleViewer) {
- setTimeout(function () {
- Zotero.openInViewer("chrome://zotero/content/debugViewer.html");
- }, 1000);
- }
- }
- }
-
- this.log = function (message, level, maxDepth, stack) {
- if (!this.enabled) {
- return;
- }
-
- if (typeof message != 'string') {
- message = Zotero.Utilities.varDump(message, 0, maxDepth);
- }
-
- if (!level) {
- level = 3;
- }
-
- // If level above debug.level value, don't display
- if (level > _level) {
- return;
- }
-
- var deltaStr = '';
- var deltaStrStore = '';
- var delta = 0;
- var d = new Date();
- if (_lastTime) {
- delta = d - _lastTime;
- }
- _lastTime = d;
- var slowPrefix = "";
- var slowSuffix = "";
- if (_slowTime && delta > _slowTime) {
- slowPrefix = "\x1b[31;40m";
- slowSuffix = "\x1b[0m";
- }
-
- delta = ("" + delta).padStart(7, "0")
-
- deltaStr = "(" + slowPrefix + "+" + delta + slowSuffix + ")";
- if (_store) {
- deltaStrStore = "(+" + delta + ")";
- }
-
- if (stack === true) {
- // Display stack starting from where this was called
- stack = Components.stack.caller;
- } else if (stack >= 0) {
- let i = stack;
- stack = Components.stack.caller;
- while(stack && i--) {
- stack = stack.caller;
- }
- } else if (_stackTrace) {
- // Stack trace enabled globally
- stack = Components.stack.caller;
- } else {
- stack = undefined;
- }
-
- if (stack) {
- message += '\n' + Zotero.Debug.stackToString(stack);
- }
-
- if (_console || _consoleViewer) {
- var output = '(' + level + ')' + deltaStr + ': ' + message;
- if (Zotero.isFx && !Zotero.isBookmarklet) {
- // Text console
- if (_console) {
- dump("zotero" + output + "\n\n");
- }
- // Console window
- if (_consoleViewer) {
- // Remove ANSI color codes. We could replace this with HTML, but it's probably
- // unnecessarily distracting/alarming to show the red in the viewer. Devs who care
- // about times should just use a terminal.
- if (slowPrefix) {
- output = output.replace(slowPrefix, '').replace(slowSuffix, '');
- }
-
- // If there's a listener, pass line immediately
- if (_consoleViewerListener) {
- _consoleViewerListener(output);
- }
- // Otherwise add to queue
- else {
- _consoleViewerQueue.push(output);
- }
- }
- } else if(window.console) {
- window.console.log(output);
- }
- }
- if (_store) {
- if (Math.random() < 1/1000) {
- // Remove initial lines if over limit
- var overage = this.count() - Zotero.Prefs.get('debug.store.limit');
- if (overage > 0) {
- _output.splice(0, Math.abs(overage));
- }
- }
- _output.push('(' + level + ')' + deltaStrStore + ': ' + message);
- }
- }
-
-
- this.get = Zotero.Promise.method(function(maxChars, maxLineLength) {
- var output = _output;
- var total = output.length;
-
- if (total == 0) {
- return "";
- }
-
- if (maxLineLength) {
- for (var i=0, len=output.length; i maxLineLength) {
- output[i] = Zotero.Utilities.ellipsize(output[i], maxLineLength, false, true);
- }
- }
- }
-
- output = output.join('\n\n');
-
- if (maxChars) {
- output = output.substr(maxChars * -1);
- // Cut at two newlines
- let matches = output.match(/^[\n]*\n\n/);
- if (matches) {
- output = output.substr(matches[0].length);
- }
- }
-
- return Zotero.getSystemInfo().then(function(sysInfo) {
- if (Zotero.isConnector) {
- return Zotero.Errors.getErrors().then(function(errors) {
- return errors.join('\n\n') +
- "\n\n" + sysInfo + "\n\n" +
- "=========================================================\n\n" +
- output;
- });
- }
- else {
- return Zotero.getErrors(true).join('\n\n') +
- "\n\n" + sysInfo + "\n\n" +
- "=========================================================\n\n" +
- output;
- }
- });
- });
-
-
- this.getConsoleViewerOutput = function () {
- var queue = _output.concat(_consoleViewerQueue);
- _consoleViewerQueue = [];
- return queue;
- }
-
-
- this.addConsoleViewerListener = function (listener) {
- this.enabled = _consoleViewer = true;
- _consoleViewerListener = listener;
- };
-
-
- this.removeConsoleViewerListener = function () {
- _consoleViewerListener = null;
- // At least for now, stop logging once console viewer is closed
- _consoleViewer = false;
- this.updateEnabled();
- };
-
-
- this.setStore = function (enable) {
- if (enable) {
- this.clear();
- }
- _store = enable;
- this.updateEnabled();
- this.storing = _store;
- }
-
-
- this.updateEnabled = function () {
- this.enabled = _console || _consoleViewer || _store;
- };
-
-
- this.count = function () {
- return _output.length;
- }
-
-
- this.clear = function () {
- _output = [];
- }
-
- /**
- * Format a stack trace for output in the same way that Error.stack does
- * @param {Components.stack} stack
- * @param {Integer} [lines=5] Number of lines to format
- */
- this.stackToString = function (stack, lines) {
- if (!lines) lines = 5;
- var str = '';
- while(stack && lines--) {
- str += '\n ' + (stack.name || '') + '@' + stack.filename
- + ':' + stack.lineNumber;
- stack = stack.caller;
- }
- return str.substr(1);
- };
-
-
- /**
- * Strip Bluebird lines from a stack trace
- *
- * @param {String} stack
- */
- this.filterStack = function (stack) {
- return stack.split(/\n/).filter(line => line.indexOf('zotero/bluebird') == -1).join('\n');
- }
-}
diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js
index 0218c3953b4..df2a90c51e7 100644
--- a/chrome/content/zotero/xpcom/http.js
+++ b/chrome/content/zotero/xpcom/http.js
@@ -621,7 +621,7 @@ Zotero.HTTP = new function() {
* through the error log and doing a fragile string comparison.
*/
_pacInstalled = function () {
- return Zotero.getErrors(true).some(val => val.indexOf("PAC file installed") == 0)
+ return Zotero.Errors.getErrors(true).some(val => val.indexOf("PAC file installed") == 0)
}
diff --git a/chrome/content/zotero/xpcom/reports.js b/chrome/content/zotero/xpcom/reports.js
new file mode 100644
index 00000000000..359bfaa2f40
--- /dev/null
+++ b/chrome/content/zotero/xpcom/reports.js
@@ -0,0 +1,545 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.Errors = new function() {
+ // 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 = [];
+
+ /**
+ * Observer for console messages
+ * @namespace
+ */
+ var ConsoleListener = {
+ "QueryInterface":XPCOMUtils.generateQI([Components.interfaces.nsIConsoleMessage,
+ Components.interfaces.nsISupports]),
+ "observe":function(msg) {
+ if(!_shouldKeepError(msg)) return;
+ if(_recentErrors.length === ERROR_BUFFER_SIZE) _recentErrors.shift();
+ _recentErrors.push(msg);
+ }
+ };
+
+ /**
+ * Determines whether to keep an error message so that it can (potentially) be reported later
+ */
+ function _shouldKeepError(msg) {
+ const skip = ['CSS Parser', 'content javascript'];
+
+ //Zotero.debug(msg);
+ try {
+ msg.QueryInterface(Components.interfaces.nsIScriptError);
+ //Zotero.debug(msg);
+ if (skip.indexOf(msg.category) != -1 || msg.flags & msg.warningFlag) {
+ return false;
+ }
+ }
+ catch (e) { }
+
+ const blacklist = [
+ "No chrome package registered for chrome://communicator",
+ '[JavaScript Error: "Components is not defined" {file: "chrome://nightly/content/talkback/talkback.js',
+ '[JavaScript Error: "document.getElementById("sanitizeItem")',
+ 'No chrome package registered for chrome://piggy-bank',
+ '[JavaScript Error: "[Exception... "\'Component is not available\' when calling method: [nsIHandlerService::getTypeFromExtension',
+ '[JavaScript Error: "this._uiElement is null',
+ 'Error: a._updateVisibleText is not a function',
+ '[JavaScript Error: "Warning: unrecognized command line flag ',
+ 'LibX:',
+ 'function skype_',
+ '[JavaScript Error: "uncaught exception: Permission denied to call method Location.toString"]',
+ 'CVE-2009-3555',
+ 'OpenGL',
+ 'trying to re-register CID',
+ 'Services.HealthReport',
+ '[JavaScript Error: "this.docShell is null"',
+ '[JavaScript Error: "downloadable font:',
+ '[JavaScript Error: "Image corrupt or truncated:',
+ '[JavaScript Error: "The character encoding of the',
+ 'nsLivemarkService.js',
+ 'Sync.Engine.Tabs',
+ 'content-sessionStore.js',
+ 'org.mozilla.appSessions',
+ 'bad script XDR magic number'
+ ];
+
+ for (var i=0; i messages[i])
+ .filter(msg => _shouldKeepError(msg));
+ } catch(e) {
+ Zotero.logError(e);
+ }
+ // Register error observer
+ Services.console.registerListener(ConsoleListener);
+
+ // Add shutdown listener to remove quit-application observer and console listener
+ Zotero.addShutdownListener(function() {
+ Services.console.unregisterListener(ConsoleListener);
+ });
+ };
+
+ this.getErrors = function (asStrings) {
+ var errors = [];
+
+ for (let msg of _startupErrors.concat(_recentErrors)) {
+ let altMessage;
+ // Remove password in malformed XML errors
+ if (msg.category == 'malformed-xml') {
+ try {
+ // msg.message is read-only, so store separately
+ altMessage = msg.message.replace(/(https?:\/\/[^:]+:)([^@]+)(@[^"]+)/, "$1****$3");
+ }
+ catch (e) {}
+ }
+
+ if (asStrings) {
+ errors.push(altMessage || msg.message)
+ }
+ else {
+ errors.push(msg);
+ }
+ }
+ return errors;
+ };
+
+ this.showReportDialog = function() {
+ var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIWindowWatcher);
+ var data = {
+ msg: Zotero.getString('errorReport.followingReportWillBeSubmitted'),
+ };
+ var io = { wrappedJSObject: { Zotero: Zotero, data: data } };
+ var win = ww.openWindow(null, "chrome://zotero/content/errorReport.xul",
+ "zotero-error-report", "chrome,centerscreen,modal", io);
+ }
+
+
+ this.generateReport = async function () {
+ let sysInfo = await Zotero.getSystemInfo();
+ return sysInfo + "\n\n" + (this.getErrors()).join('\n\n') + "\n\n"
+ }
+
+
+ this.fetchConnectorReports = async function(debug) {
+ if (!Zotero.ConnectorNotifier._listeners.length) {
+ return [];
+ }
+ if (debug) {
+ Zotero.ConnectorNotifier.notifyListeners('reports', {debug: {get: true}});
+ } else {
+ Zotero.ConnectorNotifier.notifyListeners('reports', {errors: {get: true}});
+ }
+ // We don't know if any of the listeners will respond and if they do, when,
+ // so we just wait a second.
+ await Zotero.Promise.delay(999);
+ let reports = Zotero.Server.Connector.Reports.reports;
+ Zotero.Server.Connector.Reports.reports = [];
+ return reports;
+ }
+
+
+ this.submitToZotero = async function(debug) {
+ var headers = {'Content-Type': 'text/plain'};
+ var url;
+ var zoteroBody;
+ if (debug) {
+ url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1";
+ zoteroBody = await Zotero.Debug.get(
+ Zotero.Prefs.get('debug.store.submitSize'),
+ Zotero.Prefs.get('debug.store.submitLineLength')
+ );
+ } else {
+ // TODO: change to non-debug URL once that is supported
+ url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1";
+ zoteroBody = await this.generateReport();
+ }
+ var connectorBodies = await this.fetchConnectorReports(debug);
+
+ let date = (new Date()).toUTCString();
+ let type = debug ? "Debug" : "Report";
+ let body = `----------------------------- Zotero ${type}: ${date} --------------------------------\n\n`;
+ body += zoteroBody;
+ for (let connectorBody of connectorBodies) {
+ body += `\n\n----------------------------- Connector ${type} --------------------------------\n\n`;
+ body += connectorBody;
+ }
+
+ try {
+ var xmlhttp = await Zotero.HTTP.request(
+ "POST",
+ url,
+ {
+ compressBody: true,
+ body,
+ headers,
+ logBodyLength: 30,
+ timeout: 15000,
+ requestObserver: function (req) {
+ // Don't fail during tests, with fake XHR
+ if (!req.channel) {
+ return;
+ }
+ req.channel.notificationCallbacks = {
+ onProgress: function (request, context, progress, progressMax) {},
+
+ // nsIInterfaceRequestor
+ getInterface: function (iid) {
+ try {
+ return this.QueryInterface(iid);
+ }
+ catch (e) {
+ throw Components.results.NS_NOINTERFACE;
+ }
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
+ iid.equals(Components.interfaces.nsIProgressEventSink)) {
+ return this;
+ }
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ }
+ }
+ }
+ );
+ }
+ catch (e) {
+ Zotero.logError(e);
+ if (e instanceof Zotero.HTTP.BrowserOfflineException) {
+ throw new Error(Zotero.getString('general.browserIsOffline', Zotero.appName));
+ }
+ throw new Error(Zotero.getString('general.invalidResponseServer'));
+ }
+
+ Zotero.debug(xmlhttp.responseText);
+
+ var reported = xmlhttp.responseXML.getElementsByTagName('reported');
+ if (reported.length != 1) {
+ throw new Error(Zotero.getString('errorReport.invalidResponseServer'));
+ }
+
+ var reportID = reported[0].getAttribute('reportID');
+
+ if (debug) {
+ return 'D' + reportID;
+ }
+ // TODO: remove the D once endpoint is updated for reports
+ return 'D' + reportID;
+ }
+}
+
+Zotero.Debug = new function () {
+ var _console, _stackTrace, _store, _level, _lastTime, _output = [];
+ var _slowTime = false;
+ var _colorOutput = false;
+ var _consoleViewer = false;
+ var _consoleViewerQueue = [];
+ var _consoleViewerListener;
+
+ /**
+ * Initialize debug logging
+ *
+ * Debug logging can be set in several different ways:
+ *
+ * - via the debug.log pref in the client or connector
+ * - by enabling debug output logging from the Help menu
+ * - by passing -ZoteroDebug or -ZoteroDebugText on the command line
+ *
+ * In the client, debug.log and -ZoteroDebugText enable logging via the terminal, while -ZoteroDebug
+ * enables logging via an in-app HTML-based window.
+ *
+ * @param {Integer} [forceDebugLog = 0] - Force output even if pref disabled
+ * 2: window (-ZoteroDebug)
+ * 1: text console (-ZoteroDebugText)
+ * 0: disabled
+ */
+ this.init = function (forceDebugLog = 0) {
+ _console = Zotero.Prefs.get('debug.log') || forceDebugLog == 1;
+ _consoleViewer = forceDebugLog == 2;
+ // When logging to the text console from the client on Mac/Linux, colorize output
+ if (_console && Zotero.isFx && !Zotero.isBookmarklet) {
+ _colorOutput = true;
+
+ // Time threshold in ms above which intervals should be colored red in terminal output
+ _slowTime = Zotero.Prefs.get('debug.log.slowTime');
+ }
+ _store = Zotero.Prefs.get('debug.store');
+ if (_store) {
+ Zotero.Prefs.set('debug.store', false);
+ }
+ _level = Zotero.Prefs.get('debug.level');
+ _stackTrace = Zotero.Prefs.get('debug.stackTrace');
+
+ this.storing = _store;
+ this.updateEnabled();
+
+ if (Zotero.isStandalone) {
+ // Enable dump() from window (non-XPCOM) scopes when terminal or viewer logging is enabled.
+ // (These will always go to the terminal, even in viewer mode.)
+ Zotero.Prefs.set('browser.dom.window.dump.enabled', _console || _consoleViewer, true);
+
+ if (_consoleViewer) {
+ setTimeout(function () {
+ Zotero.openInViewer("chrome://zotero/content/debugViewer.html");
+ }, 1000);
+ }
+ }
+ }
+
+ this.log = function (message, level, maxDepth, stack) {
+ if (!this.enabled) {
+ return;
+ }
+
+ if (typeof message != 'string') {
+ message = Zotero.Utilities.varDump(message, 0, maxDepth);
+ }
+
+ if (!level) {
+ level = 3;
+ }
+
+ // If level above debug.level value, don't display
+ if (level > _level) {
+ return;
+ }
+
+ var deltaStr = '';
+ var deltaStrStore = '';
+ var delta = 0;
+ var d = new Date();
+ if (_lastTime) {
+ delta = d - _lastTime;
+ }
+ _lastTime = d;
+ var slowPrefix = "";
+ var slowSuffix = "";
+ if (_slowTime && delta > _slowTime) {
+ slowPrefix = "\x1b[31;40m";
+ slowSuffix = "\x1b[0m";
+ }
+
+ delta = ("" + delta).padStart(7, "0")
+
+ deltaStr = "(" + slowPrefix + "+" + delta + slowSuffix + ")";
+ if (_store) {
+ deltaStrStore = "(+" + delta + ")";
+ }
+
+ if (stack === true) {
+ // Display stack starting from where this was called
+ stack = Components.stack.caller;
+ } else if (stack >= 0) {
+ let i = stack;
+ stack = Components.stack.caller;
+ while(stack && i--) {
+ stack = stack.caller;
+ }
+ } else if (_stackTrace) {
+ // Stack trace enabled globally
+ stack = Components.stack.caller;
+ } else {
+ stack = undefined;
+ }
+
+ if (stack) {
+ message += '\n' + Zotero.Debug.stackToString(stack);
+ }
+
+ if (_console || _consoleViewer) {
+ var output = '(' + level + ')' + deltaStr + ': ' + message;
+ if (Zotero.isFx && !Zotero.isBookmarklet) {
+ // Text console
+ if (_console) {
+ dump("zotero" + output + "\n\n");
+ }
+ // Console window
+ if (_consoleViewer) {
+ // Remove ANSI color codes. We could replace this with HTML, but it's probably
+ // unnecessarily distracting/alarming to show the red in the viewer. Devs who care
+ // about times should just use a terminal.
+ if (slowPrefix) {
+ output = output.replace(slowPrefix, '').replace(slowSuffix, '');
+ }
+
+ // If there's a listener, pass line immediately
+ if (_consoleViewerListener) {
+ _consoleViewerListener(output);
+ }
+ // Otherwise add to queue
+ else {
+ _consoleViewerQueue.push(output);
+ }
+ }
+ } else if(window.console) {
+ window.console.log(output);
+ }
+ }
+ if (_store) {
+ if (Math.random() < 1/1000) {
+ // Remove initial lines if over limit
+ var overage = this.count() - Zotero.Prefs.get('debug.store.limit');
+ if (overage > 0) {
+ _output.splice(0, Math.abs(overage));
+ }
+ }
+ _output.push('(' + level + ')' + deltaStrStore + ': ' + message);
+ }
+ }
+
+
+ this.get = async function(maxChars, maxLineLength) {
+ var output = _output;
+
+ if (maxLineLength) {
+ for (var i=0, len=output.length; i maxLineLength) {
+ output[i] = Zotero.Utilities.ellipsize(output[i], maxLineLength, false, true);
+ }
+ }
+ }
+
+ output = output.join('\n\n');
+
+ if (maxChars) {
+ output = output.substr(maxChars * -1);
+ // Cut at two newlines
+ let matches = output.match(/^[\n]*\n\n/);
+ if (matches) {
+ output = output.substr(matches[0].length);
+ }
+ }
+
+ let errors = await Zotero.Errors.generateReport();
+ return errors + "\n\n" +
+ "=========================================================\n\n" +
+ output;
+ };
+
+
+ this.getConsoleViewerOutput = function () {
+ var queue = _output.concat(_consoleViewerQueue);
+ _consoleViewerQueue = [];
+ return queue;
+ }
+
+
+ this.addConsoleViewerListener = function (listener) {
+ this.enabled = _consoleViewer = true;
+ _consoleViewerListener = listener;
+ };
+
+
+ this.removeConsoleViewerListener = function () {
+ _consoleViewerListener = null;
+ // At least for now, stop logging once console viewer is closed
+ _consoleViewer = false;
+ this.updateEnabled();
+ };
+
+
+ this.setStore = function (enable, fetchFromConnectors) {
+ if (enable) {
+ this.clear(fetchFromConnectors);
+ }
+ _store = enable;
+ this.updateEnabled();
+ this.storing = _store;
+ if (fetchFromConnectors) {
+ Zotero.ConnectorNotifier.notifyListeners('reports', {debug: {store: enable}});
+ }
+ }
+
+
+ this.updateEnabled = function () {
+ this.enabled = _console || _consoleViewer || _store;
+ };
+
+
+ this.count = function () {
+ return _output.length;
+ }
+
+
+ this.clear = function (fetchFromConnectors) {
+ _output = [];
+ if (fetchFromConnectors) {
+ Zotero.ConnectorNotifier.notifyListeners('reports', {debug: {clear: true}});
+ }
+ }
+
+ /**
+ * Format a stack trace for output in the same way that Error.stack does
+ * @param {Components.stack} stack
+ * @param {Integer} [lines=5] Number of lines to format
+ */
+ this.stackToString = function (stack, lines) {
+ if (!lines) lines = 5;
+ var str = '';
+ while(stack && lines--) {
+ str += '\n ' + (stack.name || '') + '@' + stack.filename
+ + ':' + stack.lineNumber;
+ stack = stack.caller;
+ }
+ return str.substr(1);
+ };
+
+
+ /**
+ * Strip Bluebird lines from a stack trace
+ *
+ * @param {String} stack
+ */
+ this.filterStack = function (stack) {
+ return stack.split(/\n/).filter(line => line.indexOf('zotero/bluebird') == -1).join('\n');
+ }
+
+ this.submitToZotero = async function () {
+ Zotero.Debug.setStore(false, true);
+ let debugID = await Zotero.Errors.submitToZotero(true);
+ Zotero.Debug.clear(true);
+ return debugID;
+ };
+}
diff --git a/chrome/content/zotero/xpcom/server.js b/chrome/content/zotero/xpcom/server.js
index 40c90e379ea..6f0db7b6420 100755
--- a/chrome/content/zotero/xpcom/server.js
+++ b/chrome/content/zotero/xpcom/server.js
@@ -559,7 +559,7 @@ Zotero.Server.DataListener.prototype._requestFinished = function(response) {
intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
// write response
- Zotero.debug(response, 5);
+ Zotero.debug(response.replace(/\r?\n/g, '\\n'), 5);
intlStream.writeString(response);
} finally {
intlStream.close();
diff --git a/chrome/content/zotero/xpcom/sync/syncRunner.js b/chrome/content/zotero/xpcom/sync/syncRunner.js
index cbd098a7fd2..fb1d1d87237 100644
--- a/chrome/content/zotero/xpcom/sync/syncRunner.js
+++ b/chrome/content/zotero/xpcom/sync/syncRunner.js
@@ -1322,7 +1322,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
if (e.dialogButtonText === undefined) {
var buttonText = Zotero.getString('errorReport.reportError');
var buttonCallback = function () {
- doc.defaultView.ZoteroPane.reportErrors();
+ Zotero.Errors.showReportDialog();
};
}
else {
diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js
index df74d48da84..07757fdb47f 100644
--- a/chrome/content/zotero/xpcom/utilities_internal.js
+++ b/chrome/content/zotero/xpcom/utilities_internal.js
@@ -369,7 +369,7 @@ Zotero.Utilities.Internal = {
if (typeof buttonText == 'undefined') {
buttonText = Zotero.getString('errorReport.reportError');
buttonCallback = function () {
- win.ZoteroPane.reportErrors();
+ Zotero.Errors.showReportDialog();
}
}
// If secondary button is explicitly null, just use an alert
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
index 60cd807716c..84ffcc462ca 100644
--- a/chrome/content/zotero/xpcom/zotero.js
+++ b/chrome/content/zotero/xpcom/zotero.js
@@ -130,12 +130,6 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
var _runningTimers = new Map();
var _startupTime = new Date();
- // 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 = [];
/**
* Initialize the extension
@@ -278,6 +272,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
Zotero.Prefs.init();
Zotero.Debug.init(options && options.forceDebugLog);
+ Zotero.Errors.init();
// Make sure that Zotero Standalone is not running as root
if(Zotero.isStandalone && !Zotero.isWin) _checkRoot();
@@ -399,6 +394,9 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
// Register shutdown handler to call Zotero.shutdown()
var _shutdownObserver = {observe:function() { Zotero.shutdown().done() }};
Services.obs.addObserver(_shutdownObserver, "quit-application", false);
+ Zotero.addShutdownListener(function() {
+ Services.obs.removeObserver(_shutdownObserver, "quit-application", false);
+ });
try {
Zotero.IPC.init();
@@ -409,25 +407,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
}
throw (e);
}
-
- // Get startup errors
- try {
- var messages = {};
- Services.console.getMessageArray(messages, {});
- _startupErrors = Object.keys(messages.value).map(i => messages[i])
- .filter(msg => _shouldKeepError(msg));
- } catch(e) {
- Zotero.logError(e);
- }
- // Register error observer
- Services.console.registerListener(ConsoleListener);
-
- // Add shutdown listener to remove quit-application observer and console listener
- this.addShutdownListener(function() {
- Services.obs.removeObserver(_shutdownObserver, "quit-application", false);
- Services.console.unregisterListener(ConsoleListener);
- });
-
+
// Load additional info for connector or not
if(Zotero.isConnector) {
Zotero.debug("Loading in connector mode");
@@ -1303,31 +1283,6 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
}
- this.getErrors = function (asStrings) {
- var errors = [];
-
- for (let msg of _startupErrors.concat(_recentErrors)) {
- let altMessage;
- // Remove password in malformed XML errors
- if (msg.category == 'malformed-xml') {
- try {
- // msg.message is read-only, so store separately
- altMessage = msg.message.replace(/(https?:\/\/[^:]+:)([^@]+)(@[^"]+)/, "$1****$3");
- }
- catch (e) {}
- }
-
- if (asStrings) {
- errors.push(altMessage || msg.message)
- }
- else {
- errors.push(msg);
- }
- }
- return errors;
- }
-
-
/**
* Get versions, platform, etc.
*/
@@ -1344,12 +1299,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
var extensions = yield Zotero.getInstalledExtensions();
info.extensions = extensions.join(', ');
- var str = '';
- for (var key in info) {
- str += key + ' => ' + info[key] + ', ';
- }
- str = str.substr(0, str.length - 2);
- return str;
+ return JSON.stringify(info, null, ' ');
});
@@ -2026,59 +1976,6 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
handler.preferredAction = Components.interfaces.nsIHandlerInfo.useSystemDefault;
handler.launchWithURI(uri, null);
}
-
- /**
- * Determines whether to keep an error message so that it can (potentially) be reported later
- */
- function _shouldKeepError(msg) {
- const skip = ['CSS Parser', 'content javascript'];
-
- //Zotero.debug(msg);
- try {
- msg.QueryInterface(Components.interfaces.nsIScriptError);
- //Zotero.debug(msg);
- if (skip.indexOf(msg.category) != -1 || msg.flags & msg.warningFlag) {
- return false;
- }
- }
- catch (e) { }
-
- const blacklist = [
- "No chrome package registered for chrome://communicator",
- '[JavaScript Error: "Components is not defined" {file: "chrome://nightly/content/talkback/talkback.js',
- '[JavaScript Error: "document.getElementById("sanitizeItem")',
- 'No chrome package registered for chrome://piggy-bank',
- '[JavaScript Error: "[Exception... "\'Component is not available\' when calling method: [nsIHandlerService::getTypeFromExtension',
- '[JavaScript Error: "this._uiElement is null',
- 'Error: a._updateVisibleText is not a function',
- '[JavaScript Error: "Warning: unrecognized command line flag ',
- 'LibX:',
- 'function skype_',
- '[JavaScript Error: "uncaught exception: Permission denied to call method Location.toString"]',
- 'CVE-2009-3555',
- 'OpenGL',
- 'trying to re-register CID',
- 'Services.HealthReport',
- '[JavaScript Error: "this.docShell is null"',
- '[JavaScript Error: "downloadable font:',
- '[JavaScript Error: "Image corrupt or truncated:',
- '[JavaScript Error: "The character encoding of the',
- 'nsLivemarkService.js',
- 'Sync.Engine.Tabs',
- 'content-sessionStore.js',
- 'org.mozilla.appSessions',
- 'bad script XDR magic number'
- ];
-
- for (var i=0; i
-
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index 06cbdead19e..1ac8ffc9127 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -102,9 +102,6 @@ errorReport.advanceMessage = Press %S to send the report to the Zotero develop
errorReport.stepsToReproduce = Steps to Reproduce:
errorReport.expectedResult = Expected result:
errorReport.actualResult = Actual result:
-errorReport.noNetworkConnection = No network connection
-errorReport.invalidResponseRepository = Invalid response from repository
-errorReport.repoCannotBeContacted = Repository cannot be contacted
attachmentBasePath.selectDir = Choose Base Directory
@@ -681,7 +678,6 @@ zotero.debugOutputLogging = Debug Output Logging
zotero.debugOutputLogging.linesLogged = %1$S line logged;%1$S lines logged
zotero.debugOutputLogging.dialog.title = Debug Output Submitted
zotero.debugOutputLogging.dialog.sent = Debug output has been sent to %S.\n\nThe Debug ID is D%S.
-zotero.debugOutputLogging.dialog.error = An error occurred sending debug output.
zotero.debugOutputLogging.enabledAfterRestart = Debug output logging will be enabled after %S restarts.
dragAndDrop.existingFiles = The following files already existed in the destination directory and were not copied:
diff --git a/components/zotero-service.js b/components/zotero-service.js
index 90761a4dbed..4c59eee78fa 100644
--- a/components/zotero-service.js
+++ b/components/zotero-service.js
@@ -35,7 +35,6 @@ const xpcomFilesAll = [
'zotero',
'dataDirectory',
'date',
- 'debug',
'error',
'file',
'http',
@@ -45,6 +44,7 @@ const xpcomFilesAll = [
'profile',
'progressWindow',
'proxy',
+ 'reports',
'translation/translate',
'translation/translate_firefox',
'translation/translator',