Patch over slow debug log rendering

This commit is contained in:
Evan Hahn 2021-01-14 12:01:48 -06:00 committed by Scott Nonnenberg
parent d1355d5874
commit 7884f4033e
3 changed files with 135 additions and 29 deletions

View file

@ -30,7 +30,7 @@
<h1> {{ title }} </h1>
<p> {{ debugLogExplanation }}</p>
</div>
<textarea spellcheck='false' rows='5'></textarea>
<textarea class='textarea' spellcheck='false' rows='5'></textarea>
<div class='buttons'>
<button class='grey submit'>{{ submit }}</button>
</div>

View file

@ -8,6 +8,22 @@
(function () {
window.Whisper = window.Whisper || {};
// This enum-like object describes the load state of `DebugLogView`. It's designed to be
// unidirectional; `NotStarted` → `Started` → `LogsFetchedButNotInTextarea`, etc.
const LoadState = {
NotStarted: 0,
Started: 1,
LogsFetchedButNotInTextarea: 2,
PuttingLogsInTextarea: 3,
LogsInTextarea: 4,
};
Whisper.LoadingFullLogsToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('loading') };
},
});
Whisper.LinkedCopiedToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('debugLogLinkCopied') };
@ -36,19 +52,40 @@
Whisper.ToastView.show(Whisper.LinkedCopiedToast, document.body);
},
});
/**
* The bulk of the logic in this view involves grabbing the logs from disk and putting
* them in a `<textarea>`. The first part isn't instant but is reasonably fast; setting
* the textarea's `value` takes a long time.
*
* After loading the logs into memory, we only put a small number of lines into the
* textarea. If the user clicks or scrolls the textarea, we pull the full logs, which
* can cause the system to lock up for a bit.
*
* Ideally, we'd only show a sampling of the logs and allow the user to download and
* edit them in their own editor. This is mostly a stopgap solution.
*/
Whisper.DebugLogView = Whisper.View.extend({
templateName: 'debug-log',
className: 'debug-log modal',
initialize() {
this.render();
this.$('textarea').val(i18n('loading'));
// eslint-disable-next-line more/no-then
window.log.fetch().then(text => {
this.$('textarea').val(text);
});
this.textarea = this.$('.textarea').get(0);
if (!this.textarea) {
throw new Error('textarea not found');
}
this.textarea.setAttribute('readonly', '');
this.loadState = LoadState.NotStarted;
this.putFullLogsInTextareaPlease = false;
this.fetchLogs();
},
events: {
'click .textarea': 'putFullLogsInTextarea',
'scroll .textarea': 'putFullLogsInTextarea',
'wheel .textarea': 'putFullLogsInTextarea',
'click .submit': 'submit',
'click .close': 'close',
},
@ -59,17 +96,86 @@
close: i18n('gotIt'),
debugLogExplanation: i18n('debugLogExplanation'),
},
async fetchLogs() {
if (this.loadState !== LoadState.NotStarted) {
return;
}
this.loadState = LoadState.Started;
this.textarea.value = i18n('loading');
this.$('.submit').attr('disabled', 'disabled');
this.logText = await window.log.fetch();
this.loadState = LoadState.LogsFetchedButNotInTextarea;
// This number is somewhat arbitrary; we want to show enough that it's clear that
// we need to scroll, but not so many that things get slow.
const linesToShow = Math.ceil(Math.min(window.innerHeight, 2000) / 5);
this.textarea.value = this.logText
.split(/\n/g)
.slice(0, linesToShow)
.concat(['', i18n('loading')])
.join('\n');
this.$('.submit').removeAttr('disabled');
if (this.putFullLogsInTextareaPlease) {
this.putFullLogsInTextarea();
}
},
putFullLogsInTextarea() {
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
this.putFullLogsInTextareaPlease = true;
break;
case LoadState.LogsInTextarea:
case LoadState.PuttingLogsInTextarea:
break;
case LoadState.LogsFetchedButNotInTextarea:
if (!this.logText) {
throw new Error('Expected log text to be present');
}
this.loadState = LoadState.PuttingLogsInTextarea;
Whisper.ToastView.show(Whisper.LoadingFullLogsToast, document.body);
setTimeout(() => {
this.textarea.value = this.logText;
this.textarea.removeAttribute('readonly');
this.loadState = LoadState.LogsInTextarea;
}, 0);
break;
default:
// When we can, we should make this throw a `missingCaseError`.
break;
}
},
close() {
window.closeDebugLog();
},
async submit(e) {
e.preventDefault();
const text = this.$('textarea').val();
let text;
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
return;
case LoadState.LogsFetchedButNotInTextarea:
text = this.logText;
break;
case LoadState.LogsInTextarea:
text = this.textarea.value;
break;
default:
// When we can, we should make this throw a `missingCaseError`.
return;
}
if (text.length === 0) {
return;
}
this.$('.buttons, textarea').remove();
this.$('.buttons, .textarea').remove();
this.$('.result').addClass('loading');
try {

View file

@ -328,35 +328,35 @@
{
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('textarea').val(i18n('loading'));",
"lineNumber": 44,
"line": " this.textarea = this.$('.textarea').get(0);",
"lineNumber": 74,
"reasonCategory": "usageTrusted",
"updated": "2020-05-01T17:11:39.527Z",
"reasonDetail": "Protected from arbitrary input"
"updated": "2021-01-13T23:20:15.717Z",
"reasonDetail": "Needed to get a reference to a textarea."
},
{
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('textarea').val(text);",
"lineNumber": 48,
"line": " this.$('.submit').attr('disabled', 'disabled');",
"lineNumber": 106,
"reasonCategory": "usageTrusted",
"updated": "2020-05-01T17:11:39.527Z",
"reasonDetail": "Protected from arbitrary input"
"updated": "2021-01-13T23:20:15.717Z",
"reasonDetail": "Only changes attributes; doesn't write other data to the DOM. Is also trusted input."
},
{
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " const text = this.$('textarea').val();",
"lineNumber": 67,
"line": " this.$('.submit').removeAttr('disabled');",
"lineNumber": 120,
"reasonCategory": "usageTrusted",
"updated": "2020-05-01T17:11:39.527Z",
"reasonDetail": "Protected from arbitrary input"
"updated": "2021-01-13T23:20:15.717Z",
"reasonDetail": "Only changes attributes; doesn't write other data to the DOM. Is also trusted input."
},
{
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('.buttons, textarea').remove();",
"lineNumber": 72,
"line": " this.$('.buttons, .textarea').remove();",
"lineNumber": 178,
"reasonCategory": "usageTrusted",
"updated": "2020-05-01T17:11:39.527Z",
"reasonDetail": "Protected from arbitrary input"
@ -365,7 +365,7 @@
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('.result').addClass('loading');",
"lineNumber": 73,
"lineNumber": 179,
"reasonCategory": "usageTrusted",
"updated": "2020-09-11T17:24:56.124Z",
"reasonDetail": "Static selector argument"
@ -374,7 +374,7 @@
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " el: this.$('.result'),",
"lineNumber": 82,
"lineNumber": 188,
"reasonCategory": "usageTrusted",
"updated": "2020-05-01T17:11:39.527Z",
"reasonDetail": "Protected from arbitrary input"
@ -383,7 +383,7 @@
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('.loading').removeClass('loading');",
"lineNumber": 84,
"lineNumber": 190,
"reasonCategory": "usageTrusted",
"updated": "2020-05-01T17:11:39.527Z",
"reasonDetail": "Protected from arbitrary input"
@ -392,7 +392,7 @@
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('.link').focus().select();",
"lineNumber": 86,
"lineNumber": 192,
"reasonCategory": "usageTrusted",
"updated": "2020-11-18T03:39:29.033Z",
"reasonDetail": "Protected from arbitrary input"
@ -401,7 +401,7 @@
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('.loading').removeClass('loading');",
"lineNumber": 92,
"lineNumber": 198,
"reasonCategory": "usageTrusted",
"updated": "2020-11-18T03:39:29.033Z",
"reasonDetail": "Protected from arbitrary input"
@ -410,7 +410,7 @@
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
"line": " this.$('.result').text(i18n('debugLogError'));",
"lineNumber": 93,
"lineNumber": 199,
"reasonCategory": "usageTrusted",
"updated": "2020-11-18T03:39:29.033Z",
"reasonDetail": "Protected from arbitrary input"