Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MessageList long loading logs #4180

Merged
merged 6 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/webview/MessageList.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,24 @@ class MessageList extends Component<Props> {
* Initiate round-trip handshakes with the WebView, until one succeeds.
*/
setupSendUpdateEvents = (): void => {
const timeAtSetup = Date.now();
let attempts: number = 0;
let hasLogged: boolean = false;

clearInterval(this.readyRetryInterval);
this.readyRetryInterval = setInterval(() => {
const timeElapsedMs: number = Date.now() - timeAtSetup;
if (timeElapsedMs > 1000 && hasLogged === false) {
logging.warn('Possible infinite loop in WebView "ready" setup', {
attempts,
timeElapsedMs,
});
hasLogged = true;
}

if (!this.sendUpdateEventsIsReady) {
this.sendUpdateEvents([{ type: 'ready' }]);
attempts++;
} else {
clearInterval(this.readyRetryInterval);
this.readyRetryInterval = undefined;
Expand Down
158 changes: 158 additions & 0 deletions src/webview/js/generatedEs3.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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('<div id="message-loading" class="(?:hidden)?">').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);

Expand Down Expand Up @@ -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;
};
Expand Down
69 changes: 69 additions & 0 deletions src/webview/js/js.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -10,6 +11,7 @@ import type {
WebViewUpdateEventMessagesRead,
} from '../webViewHandleUpdates';
import type { MessageListEvent } from '../webViewEventHandlers';
import { ensureUnreachable } from '../../types';

import rewriteImageUrls from './rewriteImageUrls';

Expand Down Expand Up @@ -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('<div id="message-loading" class="(?:hidden)?">').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) {
Expand Down Expand Up @@ -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;
};
Expand Down
15 changes: 13 additions & 2 deletions src/webview/webViewEventHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import config from '../config';
import type { Dispatch, GetText, Message, Narrow, Outbox, EditMessage } from '../types';
import type { BackgroundData } from './MessageList';
import type { ShowActionSheetWithOptions } from '../message/messageActionSheet';
import type { JSONableDict } from '../utils/jsonable';
import { showToast } from '../utils/info';
import { isUrlAnImage, getFullUrl } from '../utils/url';
import * as logging from '../utils/logging';
Expand Down Expand Up @@ -91,14 +92,19 @@ type MessageListEventDebug = {|
type: 'debug',
|};

type MessageListEventWarn = {|
type: 'warn',
details: JSONableDict,
|};

type MessageListEventError = {|
type: 'error',
details: {
message: string,
source: string,
line: number,
column: number,
error: mixed,
error: Error,
},
|};

Expand All @@ -124,6 +130,7 @@ export type MessageListEvent =
| MessageListEventLongPress
| MessageListEventReactionDetails
| MessageListEventDebug
| MessageListEventWarn
| MessageListEventError
| MessageListEventMention;

Expand Down Expand Up @@ -263,8 +270,12 @@ export const handleMessageListEvent = (props: Props, _: GetText, event: MessageL
console.debug(props, event); // eslint-disable-line
break;

case 'warn':
logging.warn('WebView warning', event.details);
break;

case 'error':
logging.error(`WebView exception: ${JSON.stringify(event.details)}`);
logging.error('WebView error', event.details);
break;

default:
Expand Down
4 changes: 2 additions & 2 deletions src/webview/webViewHandleUpdates.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export type WebViewUpdateEventReady = {|
type: 'ready',
|};

export type WebViewUpdateEventMessagesRead = {
export type WebViewUpdateEventMessagesRead = {|
type: 'read',
messageIds: number[],
};
|};

export type WebViewUpdateEvent =
| WebViewUpdateEventContent
Expand Down