commit f44264cd4d757c46f4177e537f454d066a4272f1
parent 9c0befceeb2dfd10154b0cb6498dfd2b8f308b16
Author: Dan Stillman <dstillman@zotero.org>
Date: Sat, 14 Jan 2017 17:03:22 -0500
Add HTML-based console viewer for easier real-time debug output
Since 1) debug output logging via the prefs isn't necessarily possible
for startup errors in Standalone, 2) real-time output is prohibitively
slow and has a miniscule scrollback buffer on Windows unless you use a
Cygwin or Git terminal, and 3) copying/pasting/emailing was annoying
anyway, make -ZoteroDebug open a popup window that shows errors and
debug output and allows submitting straight to the server with a Debug
ID.
This should replace the existing debug output viewer as well, but that's
less of a priority.
-ZoteroDebugText or the debug.log pref can still be used to dump to the
terminal.
Diffstat:
4 files changed, 414 insertions(+), 29 deletions(-)
diff --git a/chrome/content/zotero/debugViewer.html b/chrome/content/zotero/debugViewer.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>Debug Output</title>
+ <script src="include.js"></script>
+ <script src="debugViewer.js"></script>
+
+ <style>
+ body {
+ margin: 0;
+ }
+
+ header {
+ position: fixed;
+ top: 0;
+ background: lightgrey;
+ display: flex;
+ align-items: center;
+ width: calc(100% - 20px);
+ height: 18px;
+ padding: 10px;
+ margin-bottom: 10px;
+ font-family: sans-serif;
+ font-size: 11pt;
+ }
+
+ header > * {
+ margin-right: 10px;
+ }
+
+ progress {
+ width: 125px;
+ }
+
+ #debug-id {
+ font-weight: bold;
+ }
+
+ #submit-result {
+ line-height: 1.25em;
+ }
+
+ #submit-result-copy-id {
+ cursor: pointer;
+ padding-left: 2px;
+ }
+
+ #submit-error {
+ font-weight: bold;
+ color: red;
+ }
+
+ #content {
+ margin-top: 38px;
+ padding: 10px 9px;
+ font-family: Monaco, Consolas, Inconsolata, monospace;
+ font-size: 9pt;
+ }
+
+ #errors {
+ padding-bottom: 12px;
+ border-bottom: 1px lightgray solid;
+ white-space: pre-wrap;
+ }
+
+
+
+ /*
+ CSS tooltip, adapted from http://stackoverflow.com/a/25836471
+ */
+ [data-tooltip] {
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ padding: 2px;
+ }
+ [data-tooltip]:before {
+ content: attr(data-tooltip);
+ display: none;
+ position: absolute;
+ background: #000;
+ color: #fff;
+ padding: 4px 8px;
+ font-size: 12px;
+ font-family: sans-serif;
+ line-height: 1.4;
+ text-align: center;
+ border-radius: 4px;
+
+ left: 50%;
+ transform: translateX(-50%);
+
+ top: 100%;
+ margin-top: 6px;
+
+ white-space: nowrap;
+ }
+ [data-tooltip]:after {
+ content: '';
+ display: none;
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+
+ left: 50%;
+ margin-left: -6px;
+
+ top: 100%;
+ border-width: 0 6px 6px;
+ border-bottom-color: #000;
+ }
+ /* Show the tooltip when hovering */
+ [data-tooltip]:hover:before,
+ [data-tooltip]:hover:after {
+ display: block;
+ z-index: 50;
+ }
+ </style>
+ </head>
+ <body>
+ <header>
+ <button id="submit-button" onclick="submit(this)" disabled>Submit…</button>
+ <button id="clear-button" onclick="clearOutput(this)" disabled>Clear</button>
+ <progress id="submit-progress" hidden></progress>
+ <p id="submit-result" hidden>
+ Submitted with Debug ID <span id="debug-id"></span>
+ <span id="submit-result-copy-id" onclick="copyIDToClipboard(this)">📋</span>
+ </p>
+ <p id="submit-error" hidden></p>
+ </header>
+ <div id="content">
+ <div id="errors"></div>
+ <div id="output"></div>
+ </div>
+ </body>
+</html>
diff --git a/chrome/content/zotero/debugViewer.js b/chrome/content/zotero/debugViewer.js
@@ -0,0 +1,189 @@
+"use strict";
+
+var interval = 1000;
+var intervalID;
+var stopping = false;
+
+function start() {
+ updateErrors().then(function () {
+ if (stopping) return;
+
+ addInitialOutput();
+ Zotero.Debug.addConsoleViewerListener(addLine)
+ intervalID = setInterval(() => updateErrors(), interval);
+ });
+}
+
+function stop() {
+ stopping = true;
+ if (intervalID) {
+ clearInterval(intervalID);
+ intervalID = null;
+ }
+ Zotero.Debug.removeConsoleViewerListener()
+}
+
+function updateErrors() {
+ return Zotero.getSystemInfo()
+ .then(function (sysInfo) {
+ if (stopping) return;
+
+ var errors = Zotero.getErrors(true);
+ var errorStr = errors.length ? errors.join('\n\n') + '\n\n' : '';
+
+ var scroll = atPageBottom();
+
+ document.getElementById('errors').textContent = errorStr + sysInfo;
+
+ // TODO: This doesn't seem to work for some reason -- when errors are logged, it doesn't stay
+ // at the bottom
+ if (scroll) {
+ scrollToPageBottom();
+ }
+ });
+}
+
+function addInitialOutput() {
+ Zotero.Debug.getConsoleViewerOutput().forEach(function (line) {
+ addLine(line);
+ });
+}
+
+function addLine(line) {
+ var scroll = atPageBottom()
+
+ var p = document.createElement('p');
+ p.textContent = line;
+ var output = document.getElementById('output');
+ output.appendChild(p);
+
+ // If scrolled to the bottom of the page, stay there
+ if (scroll) {
+ scrollToPageBottom();
+ }
+
+ document.getElementById('submit-button').removeAttribute('disabled');
+ document.getElementById('clear-button').removeAttribute('disabled');
+}
+
+function atPageBottom() {
+ return (window.innerHeight + window.scrollY) >= document.body.offsetHeight - 100;
+}
+
+function scrollToPageBottom() {
+ window.scrollTo(0, document.body.scrollHeight);
+}
+
+function submit(button) {
+ button.setAttribute('disabled', '');
+ clearSubmitStatus();
+
+ Components.utils.import("resource://zotero/config.js");
+ var url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1";
+ var output = document.getElementById('errors').textContent
+ + "\n\n" + "=========================================================\n\n"
+ + Array.from(document.getElementById('output').childNodes).map(p => p.textContent).join("\n\n");
+ var pm = document.getElementById('submit-progress');
+ pm.removeAttribute('hidden');
+
+ Zotero.HTTP.request(
+ "POST",
+ url,
+ {
+ compressBody: true,
+ body: output,
+ logBodyLength: 30,
+ timeout: 30000,
+ // Update progress meter
+ requestObserver: function (req) {
+ req.channel.notificationCallbacks = {
+ onProgress: function (request, context, progress, progressMax) {
+ if (!pm.value || progress > pm.value) {
+ pm.value = progress;
+ }
+ if (!pm.max || progressMax > pm.max) {
+ pm.max = 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;
+ },
+
+ }
+ }
+ }
+ )
+ .then(function (xmlhttp) {
+ var reported = xmlhttp.responseXML.getElementsByTagName('reported');
+ if (reported.length != 1) {
+ showSubmitError(e);
+ return false;
+ }
+
+ showSubmitResult(reported[0].getAttribute('reportID'));
+ })
+ .catch(function (e) {
+ showSubmitError(e);
+ return false;
+ })
+ .finally(function () {
+ pm.setAttribute('hidden', '');
+ button.removeAttribute('disabled');
+ });
+}
+
+function showSubmitResult(id) {
+ var elem = document.getElementById('submit-result');
+ elem.removeAttribute('hidden');
+ document.getElementById('debug-id').textContent = "D" + id;
+ var copyID = document.getElementById('submit-result-copy-id');
+ copyID.style.visibility = 'visible';
+ copyID.setAttribute('data-tooltip', 'Copy ID to Clipboard');
+}
+
+function copyIDToClipboard(elem) {
+ var id = document.getElementById('debug-id').textContent;
+ Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Components.interfaces.nsIClipboardHelper)
+ .copyString(id);
+ elem.setAttribute('data-tooltip', 'Copied');
+ setTimeout(() => elem.style.visibility = 'hidden', 750);
+}
+
+function showSubmitError(e) {
+ var elem = document.getElementById('submit-error');
+ elem.removeAttribute('hidden');
+ elem.textContent = "Error submitting output";
+ Components.utils.reportError(e);
+ Zotero.debug(e, 1);
+}
+
+function clearSubmitStatus() {
+ document.getElementById('submit-result').setAttribute('hidden', '');
+ document.getElementById('submit-error').setAttribute('hidden', '');
+}
+
+function clearOutput(button) {
+ button.setAttribute('disabled', '');
+ document.getElementById('output').textContent = '';
+ clearSubmitStatus();
+}
+
+window.addEventListener('load', start);
+window.addEventListener("unload", stop);
diff --git a/chrome/content/zotero/xpcom/debug.js b/chrome/content/zotero/xpcom/debug.js
@@ -25,19 +25,38 @@
Zotero.Debug = new function () {
- var _console, _consolePref, _stackTrace, _store, _level, _lastTime, _output = [];
+ var _console, _stackTrace, _store, _level, _lastTime, _output = [];
var _slowTime = false;
var _colorOutput = false;
+ var _consoleViewer = false;
+ var _consoleViewerQueue = [];
+ var _consoleViewerListener;
- this.init = function (forceDebugLog) {
- _consolePref = Zotero.Prefs.get('debug.log');
- _console = _consolePref || forceDebugLog;
- if (_console && Zotero.isFx && !Zotero.isBookmarklet && (!Zotero.isWin || _consolePref)) {
+ /**
+ * 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 in the Advanced prefs in the client
+ * - 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;
- }
- if (_colorOutput) {
- // Time threshold in milliseconds above which intervals
- // should be colored red in terminal output
+
+ // 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');
@@ -48,15 +67,23 @@ Zotero.Debug = new function () {
_stackTrace = Zotero.Prefs.get('debug.stackTrace');
this.storing = _store;
- this.enabled = _console || _store;
+ this.updateEnabled();
if (Zotero.isStandalone) {
- Zotero.Prefs.set('browser.dom.window.dump.enabled', _console, true);
+ // 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, stack) {
- if (!_console && !_store) {
+ if (!this.enabled) {
return;
}
@@ -118,21 +145,23 @@ Zotero.Debug = new function () {
message += '\n' + Zotero.Debug.stackToString(stack);
}
- if (_console) {
- var output = 'zotero(' + level + ')' + deltaStr + ': ' + message;
- if(Zotero.isFx && !Zotero.isBookmarklet) {
- // On Windows, where the text console (-console) is inexplicably glacial,
- // log to the Browser Console instead if only the -ZoteroDebug flag is used.
- // Developers can use the debug.log/debug.time prefs and the Cygwin text console.
- //
- // TODO: Get rid of the filename and line number
- if (!_consolePref && Zotero.isWin && !Zotero.isStandalone) {
- var console = Components.utils.import("resource://gre/modules/Console.jsm", {}).console;
- console.log(output);
+ if (_console || _consoleViewer) {
+ var output = '(' + level + ')' + deltaStr + ': ' + message;
+ if (Zotero.isFx && !Zotero.isBookmarklet) {
+ // Text console
+ if (_console) {
+ dump("zotero" + output + "\n\n");
}
- // Otherwise dump to the text console
- else {
- dump(output + "\n\n");
+ // Console window
+ if (_consoleViewer) {
+ // 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);
@@ -193,6 +222,26 @@ Zotero.Debug = new function () {
});
+ this.getConsoleViewerOutput = function () {
+ var queue = _consoleViewerQueue;
+ _consoleViewerQueue = [];
+ return queue;
+ }
+
+
+ this.addConsoleViewerListener = function (listener) {
+ _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();
@@ -200,10 +249,14 @@ Zotero.Debug = new function () {
_store = enable;
this.storing = _store;
- this.enabled = _console || _store;
}
+ this.updateEnabled = function () {
+ this.enabled = _console || _consoleViewer || _store;
+ };
+
+
this.count = function () {
return _output.length;
}
diff --git a/components/zotero-service.js b/components/zotero-service.js
@@ -485,9 +485,13 @@ function ZoteroCommandLineHandler() {}
ZoteroCommandLineHandler.prototype = {
/* nsICommandLineHandler */
handle : function(cmdLine) {
- // Force debug output
+ // Force debug output to window
if (cmdLine.handleFlag("ZoteroDebug", false)) {
- zInitOptions.forceDebugLog = true;
+ zInitOptions.forceDebugLog = 2;
+ }
+ // Force debug output to text console
+ else if (cmdLine.handleFlag("ZoteroDebugText", false)) {
+ zInitOptions.forceDebugLog = 1;
}
// handler to open Zotero pane at startup in Zotero for Firefox