mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 14:05:04 +08:00
Merge pull request #4111 from matrix-org/matthew/dom-leaks
Fix two big DOM leaks which were locking Chrome solid.
This commit is contained in:
commit
2bd32050fc
@ -20,7 +20,7 @@ import * as HtmlUtils from '../../../HtmlUtils';
|
|||||||
import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils';
|
import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils';
|
||||||
import {formatTime} from '../../../DateUtils';
|
import {formatTime} from '../../../DateUtils';
|
||||||
import {MatrixEvent} from 'matrix-js-sdk';
|
import {MatrixEvent} from 'matrix-js-sdk';
|
||||||
import {pillifyLinks} from '../../../utils/pillify';
|
import {pillifyLinks, unmountPills} from '../../../utils/pillify';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
@ -53,6 +53,7 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||||||
this.state = {canRedact, sendStatus: event.getAssociatedStatus()};
|
this.state = {canRedact, sendStatus: event.getAssociatedStatus()};
|
||||||
|
|
||||||
this._content = createRef();
|
this._content = createRef();
|
||||||
|
this._pills = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAssociatedStatusChanged = () => {
|
_onAssociatedStatusChanged = () => {
|
||||||
@ -81,7 +82,7 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||||||
pillifyLinks() {
|
pillifyLinks() {
|
||||||
// not present for redacted events
|
// not present for redacted events
|
||||||
if (this._content.current) {
|
if (this._content.current) {
|
||||||
pillifyLinks(this._content.current.children, this.props.mxEvent);
|
pillifyLinks(this._content.current.children, this.props.mxEvent, this._pills);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
unmountPills(this._pills);
|
||||||
const event = this.props.mxEvent;
|
const event = this.props.mxEvent;
|
||||||
if (event.localRedactionEvent()) {
|
if (event.localRedactionEvent()) {
|
||||||
event.localRedactionEvent().off("status", this._onAssociatedStatusChanged);
|
event.localRedactionEvent().off("status", this._onAssociatedStatusChanged);
|
||||||
|
@ -30,7 +30,7 @@ import { _t } from '../../../languageHandler';
|
|||||||
import * as ContextMenu from '../../structures/ContextMenu';
|
import * as ContextMenu from '../../structures/ContextMenu';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import ReplyThread from "../elements/ReplyThread";
|
import ReplyThread from "../elements/ReplyThread";
|
||||||
import {pillifyLinks} from '../../../utils/pillify';
|
import {pillifyLinks, unmountPills} from '../../../utils/pillify';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
|
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
|
||||||
import {toRightOf} from "../../structures/ContextMenu";
|
import {toRightOf} from "../../structures/ContextMenu";
|
||||||
@ -92,6 +92,7 @@ export default createReactClass({
|
|||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
|
this._pills = [];
|
||||||
if (!this.props.editState) {
|
if (!this.props.editState) {
|
||||||
this._applyFormatting();
|
this._applyFormatting();
|
||||||
}
|
}
|
||||||
@ -103,7 +104,7 @@ export default createReactClass({
|
|||||||
// pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer
|
// pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer
|
||||||
// are still sent as plaintext URLs. If these are ever pillified in the composer,
|
// are still sent as plaintext URLs. If these are ever pillified in the composer,
|
||||||
// we should be pillify them here by doing the linkifying BEFORE the pillifying.
|
// we should be pillify them here by doing the linkifying BEFORE the pillifying.
|
||||||
pillifyLinks([this._content.current], this.props.mxEvent);
|
pillifyLinks([this._content.current], this.props.mxEvent, this._pills);
|
||||||
HtmlUtils.linkifyElement(this._content.current);
|
HtmlUtils.linkifyElement(this._content.current);
|
||||||
this.calculateUrlPreview();
|
this.calculateUrlPreview();
|
||||||
|
|
||||||
@ -146,6 +147,7 @@ export default createReactClass({
|
|||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
|
unmountPills(this._pills);
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldComponentUpdate: function(nextProps, nextState) {
|
shouldComponentUpdate: function(nextProps, nextState) {
|
||||||
|
@ -490,6 +490,7 @@ export default class BasicMessageEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener("selectionchange", this._onSelectionChange);
|
||||||
this._editorRef.removeEventListener("input", this._onInput, true);
|
this._editorRef.removeEventListener("input", this._onInput, true);
|
||||||
this._editorRef.removeEventListener("compositionstart", this._onCompositionStart, true);
|
this._editorRef.removeEventListener("compositionstart", this._onCompositionStart, true);
|
||||||
this._editorRef.removeEventListener("compositionend", this._onCompositionEnd, true);
|
this._editorRef.removeEventListener("compositionend", this._onCompositionEnd, true);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -21,7 +21,20 @@ import SettingsStore from "../settings/SettingsStore";
|
|||||||
import {PushProcessor} from 'matrix-js-sdk/src/pushprocessor';
|
import {PushProcessor} from 'matrix-js-sdk/src/pushprocessor';
|
||||||
import * as sdk from '../index';
|
import * as sdk from '../index';
|
||||||
|
|
||||||
export function pillifyLinks(nodes, mxEvent) {
|
/**
|
||||||
|
* Recurses depth-first through a DOM tree, converting matrix.to links
|
||||||
|
* into pills based on the context of a given room. Returns a list of
|
||||||
|
* the resulting React nodes so they can be unmounted rather than leaking.
|
||||||
|
*
|
||||||
|
* @param {Node[]} nodes - a list of sibling DOM nodes to traverse to try
|
||||||
|
* to turn into pills.
|
||||||
|
* @param {MatrixEvent} mxEvent - the matrix event which the DOM nodes are
|
||||||
|
* part of representing.
|
||||||
|
* @param {Node[]} pills: an accumulator of the DOM nodes which contain
|
||||||
|
* React components which have been mounted as part of this.
|
||||||
|
* The initial caller should pass in an empty array to seed the accumulator.
|
||||||
|
*/
|
||||||
|
export function pillifyLinks(nodes, mxEvent, pills) {
|
||||||
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
|
||||||
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
|
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
|
||||||
let node = nodes[0];
|
let node = nodes[0];
|
||||||
@ -45,6 +58,7 @@ export function pillifyLinks(nodes, mxEvent) {
|
|||||||
|
|
||||||
ReactDOM.render(pill, pillContainer);
|
ReactDOM.render(pill, pillContainer);
|
||||||
node.parentNode.replaceChild(pillContainer, node);
|
node.parentNode.replaceChild(pillContainer, node);
|
||||||
|
pills.push(pillContainer);
|
||||||
// Pills within pills aren't going to go well, so move on
|
// Pills within pills aren't going to go well, so move on
|
||||||
pillified = true;
|
pillified = true;
|
||||||
|
|
||||||
@ -102,6 +116,7 @@ export function pillifyLinks(nodes, mxEvent) {
|
|||||||
|
|
||||||
ReactDOM.render(pill, pillContainer);
|
ReactDOM.render(pill, pillContainer);
|
||||||
roomNotifTextNode.parentNode.replaceChild(pillContainer, roomNotifTextNode);
|
roomNotifTextNode.parentNode.replaceChild(pillContainer, roomNotifTextNode);
|
||||||
|
pills.push(pillContainer);
|
||||||
}
|
}
|
||||||
// Nothing else to do for a text node (and we don't need to advance
|
// Nothing else to do for a text node (and we don't need to advance
|
||||||
// the loop pointer because we did it above)
|
// the loop pointer because we did it above)
|
||||||
@ -111,9 +126,26 @@ export function pillifyLinks(nodes, mxEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node.childNodes && node.childNodes.length && !pillified) {
|
if (node.childNodes && node.childNodes.length && !pillified) {
|
||||||
pillifyLinks(node.childNodes, mxEvent);
|
pillifyLinks(node.childNodes, mxEvent, pills);
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.nextSibling;
|
node = node.nextSibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmount all the pill containers from React created by pillifyLinks.
|
||||||
|
*
|
||||||
|
* It's critical to call this after pillifyLinks, otherwise
|
||||||
|
* Pills will leak, leaking entire DOM trees via the event
|
||||||
|
* emitter on BaseAvatar as per
|
||||||
|
* https://github.com/vector-im/riot-web/issues/12417
|
||||||
|
*
|
||||||
|
* @param {Node[]} pills - array of pill containers whose React
|
||||||
|
* components should be unmounted.
|
||||||
|
*/
|
||||||
|
export function unmountPills(pills) {
|
||||||
|
for (const pillContainer of pills) {
|
||||||
|
ReactDOM.unmountComponentAtNode(pillContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user