From aef73e6ad6c0b01a4d88f580993bffc05a6629e4 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 1 Jul 2020 11:40:42 -0700 Subject: [PATCH] msglist: Hasty logging code, for long waits with placeholders. In steady state, these changes should be reverted or structured a bit better; it feels a bit haphazard. If the placeholders are still visible after 10 seconds, send a log to Sentry with all the events the WebView has received, with timestamps of receipt. Redact any `auth` fields by replacing the value with "redacted", and redact any `content` fields by extracting just the opening tag for the "message-loading" div so we can see if it has the "hidden" class. But we want to debug #4156 ASAP, [1]: https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/.23M4156.20Message.20List.20placeholders/near/921700 --- src/webview/js/generatedEs3.js | 158 +++++++++++++++++++++++++++++++++ src/webview/js/js.js | 69 ++++++++++++++ 2 files changed, 227 insertions(+) diff --git a/src/webview/js/generatedEs3.js b/src/webview/js/generatedEs3.js index 3b7c4fac2da..681f740c02d 100644 --- a/src/webview/js/generatedEs3.js +++ b/src/webview/js/generatedEs3.js @@ -14,6 +14,91 @@ export default ` var compiledWebviewJs = (function (exports) { 'use strict'; + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + } + + function ownKeys(object, enumerableOnly) { + var keys = Object.keys(object); + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(object); + if (enumerableOnly) symbols = symbols.filter(function (sym) { + return Object.getOwnPropertyDescriptor(object, sym).enumerable; + }); + keys.push.apply(keys, symbols); + } + + return keys; + } + + function _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + + if (i % 2) { + ownKeys(Object(source), true).forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + + return target; + } + + function _objectWithoutPropertiesLoose(source, excluded) { + if (source == null) return {}; + var target = {}; + var sourceKeys = Object.keys(source); + var key, i; + + for (i = 0; i < sourceKeys.length; i++) { + key = sourceKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + target[key] = source[key]; + } + + return target; + } + + function _objectWithoutProperties(source, excluded) { + if (source == null) return {}; + + var target = _objectWithoutPropertiesLoose(source, excluded); + + var key, i; + + if (Object.getOwnPropertySymbols) { + var sourceSymbolKeys = Object.getOwnPropertySymbols(source); + + for (i = 0; i < sourceSymbolKeys.length; i++) { + key = sourceSymbolKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; + target[key] = source[key]; + } + } + + return target; + } + var inlineApiRoutes = ['^/user_uploads/', '^/thumbnail$', '^/avatar/'].map(function (r) { return new RegExp(r); }); @@ -127,6 +212,73 @@ var compiledWebviewJs = (function (exports) { return true; }; + var isTrackingLongLoad = true; + var eventsDuringLongLoad = []; + + var logLongLoad = function logLongLoad() { + if (eventsDuringLongLoad === null) { + throw new Error(); + } + + var loggableEvents = eventsDuringLongLoad.map(function (eventWithTimestamp) { + var placeholdersDivTagFromContent = function placeholdersDivTagFromContent(content) { + var match = new RegExp('
').exec(content); + return match !== null ? match[0] : null; + }; + + var content = eventWithTimestamp.content, + auth = eventWithTimestamp.auth, + rest = _objectWithoutProperties(eventWithTimestamp, ["content", "auth"]); + + switch (eventWithTimestamp.type) { + case 'content': + { + return _objectSpread2({}, rest, { + auth: 'redacted', + content: placeholdersDivTagFromContent(eventWithTimestamp.content) + }); + } + + case 'read': + case 'ready': + case 'fetching': + return rest; + + case 'typing': + { + return _objectSpread2({}, rest, { + content: placeholdersDivTagFromContent(eventWithTimestamp.content) + }); + } + + default: + return { + type: eventWithTimestamp.type, + timestamp: eventWithTimestamp.timestamp + }; + } + }); + sendMessage({ + type: 'warn', + details: { + loggableEvents: loggableEvents + } + }); + }; + + var maybeLogLongLoad = function maybeLogLongLoad() { + var placeholdersDiv = document.getElementById('message-loading'); + + if (placeholdersDiv && !placeholdersDiv.classList.contains('hidden')) { + logLongLoad(); + } + + isTrackingLongLoad = false; + eventsDuringLongLoad = null; + }; + + setTimeout(maybeLogLongLoad, 10000); + var showHideElement = function showHideElement(elementId, show) { var element = document.getElementById(elementId); @@ -479,6 +631,12 @@ var compiledWebviewJs = (function (exports) { var updateEvents = JSON.parse(decodedData); updateEvents.forEach(function (uevent) { eventUpdateHandlers[uevent.type](uevent); + + if (isTrackingLongLoad && eventsDuringLongLoad !== null) { + eventsDuringLongLoad.push(_objectSpread2({}, uevent, { + timestamp: Date.now() + })); + } }); scrollEventsDisabled = false; }; diff --git a/src/webview/js/js.js b/src/webview/js/js.js index ac3e2f47356..1460404c814 100644 --- a/src/webview/js/js.js +++ b/src/webview/js/js.js @@ -1,6 +1,7 @@ /* @flow strict-local */ /* eslint-disable no-useless-return */ import type { Auth } from '../../types'; +import type { JSONable } from '../../utils/jsonable'; import type { WebViewUpdateEvent, WebViewUpdateEventContent, @@ -10,6 +11,7 @@ import type { WebViewUpdateEventMessagesRead, } from '../webViewHandleUpdates'; import type { MessageListEvent } from '../webViewEventHandlers'; +import { ensureUnreachable } from '../../types'; import rewriteImageUrls from './rewriteImageUrls'; @@ -156,6 +158,66 @@ window.onerror = (message: string, source: string, line: number, column: number, return true; }; +let isTrackingLongLoad = true; +let eventsDuringLongLoad: Array<{ ...WebViewUpdateEvent, timestamp: number }> | null = []; + +const logLongLoad = () => { + if (eventsDuringLongLoad === null) { + throw new Error(); + } + const loggableEvents: JSONable[] = eventsDuringLongLoad.map(eventWithTimestamp => { + const placeholdersDivTagFromContent = (content: string) => { + const match = new RegExp('
').exec(content); + return match !== null ? match[0] : null; + }; + // $FlowFixMe (some events don't have content or auth) + const { content, auth, ...rest } = eventWithTimestamp; /* eslint-disable-line no-unused-vars */ + switch (eventWithTimestamp.type) { + case 'content': { + return { + ...rest, + auth: 'redacted', + content: placeholdersDivTagFromContent(eventWithTimestamp.content), + }; + } + case 'read': + case 'ready': + case 'fetching': + return rest; + case 'typing': { + return { + ...rest, + content: placeholdersDivTagFromContent(eventWithTimestamp.content), + }; + } + default: + ensureUnreachable(eventWithTimestamp); + return { + type: eventWithTimestamp.type, + timestamp: eventWithTimestamp.timestamp, + }; + } + }); + + sendMessage({ + type: 'warn', + details: { + loggableEvents, + }, + }); +}; + +const maybeLogLongLoad = () => { + const placeholdersDiv = document.getElementById('message-loading'); + if (placeholdersDiv && !placeholdersDiv.classList.contains('hidden')) { + logLongLoad(); + } + isTrackingLongLoad = false; + eventsDuringLongLoad = null; +}; + +setTimeout(maybeLogLongLoad, 10000); + const showHideElement = (elementId: string, show: boolean) => { const element = document.getElementById(elementId); if (element) { @@ -609,6 +671,13 @@ const handleMessageEvent: MessageEventListener = e => { updateEvents.forEach((uevent: WebViewUpdateEvent) => { // $FlowFixMe eventUpdateHandlers[uevent.type](uevent); + if (isTrackingLongLoad && eventsDuringLongLoad !== null) { + // $FlowFixMe the spread seems to confuse Flow, but this is likely correct + eventsDuringLongLoad.push({ + ...uevent, + timestamp: Date.now(), + }); + } }); scrollEventsDisabled = false; };