-
Notifications
You must be signed in to change notification settings - Fork 442
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
Improve performance of the list of chat messages #1271
Merged
nickvergessen
merged 15 commits into
master
from
improve-performance-of-the-list-of-chat-messages
Nov 20, 2018
Merged
Improve performance of the list of chat messages #1271
nickvergessen
merged 15 commits into
master
from
improve-performance-of-the-list-of-chat-messages
Nov 20, 2018
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
danxuliu
force-pushed
the
improve-performance-of-the-list-of-chat-messages
branch
6 times, most recently
from
November 20, 2018 09:52
1d21ebf
to
3d3a470
Compare
danxuliu
added
3. to review
backport-request
bug
enhancement
feature: chat 💬
Chat and system messages
feature: frontend 🖌️
"Web UI" client
and removed
2. developing
labels
Nov 20, 2018
Currently models are only appended, so there is no need to handle an insertion. Moreover, it is not handled anywhere else, so even if a model was inserted the result would be broken. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
The calculations to get the scroll position were based on both the previous newest comment and on the current newest comment just added. However, as the current newest comment is the one that needs to be fully shown the calculations can be solved using only that element. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This avoids iterating over all the comments each time a new one is added. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Each time a new node is added to the document a reflow and a render is triggered. This can cause the UI to become unresponsive when a lot of nodes are added in a row, for example, when loading the messages for the first time after entering a room. To mitigate that now the DOM elements for the messages to add are buffered using a document fragment. Instead of adding the element for each model directly to the document now they are added to the document fragment, which does not cause a reflow nor a render, and once all the elements have been created then they are added at once to the document, which causes a single reflow and render instead of one for each element. Despite that, note that when there are thousands of messages in the list even a single reflow and render can make the UI hang for a few seconds; using a virtual list is probably the only way to solve this. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Despite the browser optimizations there is a limit in the number of elements that can be added to a document before the browser becomes sluggish when the document is further modified. This problem can be clearly seen in the list of chat messages with long conversations; adding several messages to the document all at once instead of one by one can improve the performance of the initial load, but when the number of messages grows too much any other addition causes the browser to become unresponsive. In this case adding several messages at once (which may not be possible either during a regular chat) does not help, because the real culprit here is that the browser needs to layout again all the previous messages. To solve that problem this commit introduces an initial implementation of a virtual list; the virtual list keeps in the document only those elements that are currently visible and refreshes them as needed when the list is scrolled. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Before the 3.0.0 release jQuery rounded the height to the nearest integer, so the height returned by jQuery is not the correct value in browsers with subpixel accuracy (positions and sizes with decimal values), like Firefox. Due to this all the height related functions of jQuery need to be replaced with custom versions that return the height as a float instead of an integer. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Although similar, prepending elements is not just a "mirrored" append; besides caching the position and size of the added elements it is also necessary to update the position of the rest of the elements, as it will have have changed due to the prepended elements. However, it is not necessary to show those elements to update their position; it is assumed that the existing elements will not be modified, so their position can be updated based on the height of the prepended elements (which needs to account for a possible collapse of the margins between the first existing element and the first prepended element). Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
When the size of the container changes the position and size of all the elements in the list could change. The virtual list relies on the cached values for those properties, so they must be cached again when they change. The values need to be cached from elements in the document, but it is not possible to add all the elements at once, even if temporary, to cache their values. Thus the reload is an incremental process that starts with the visible elements and progressively updates the rest of elements; during that process the list can be scrolled only to those elements already loaded, as those are the only ones with valid cached values. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
The virtual list requires that its internal wrappers use an absolute position. Due to that absolute position the padding of the container does not affect the wrappers, so the desired padding must be applied through its left and right position. As the virtual list keeps only a subset of its elements in the DOM the ":first-child" pseudo-selector no longer refers to the actual first child element, but to the first one currently in the DOM; it would be necessary to apply the CSS rules using a specific CSS class set only in the desired element. However, as the first comment always includes the date separator, which already has a top margin, the top padding is not really needed in the first comment, so it was simply removed. Moving the message list between the main view and the sidebar changes its size, and thus it is necessary to reload the virtual list; when the virtual list is reloaded it is ensured that the last visible element will still be visible after the reload, so the chat view no longer needs to explicitly handle that. In a similar way, the message list also needs to be reloaded when the window is resized, or when the chat view is in the main view and the sidebar is opened or closed, as those actions change the size of the main view. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Firefox automatically includes scrollable elements in the sequential keyboard navigation, but it needs to be explicitly done for Chromium. Moreover, setting "tabindex" is also needed in Chromium to be able to scroll by keeping a key pressed after focusing on the scrollable element by clicking on one of its children; in Firefox the event keeps being processed and the scrolling goes on until the key is released, but in Chromium, when "tabindex" is not set, the scrolling stops as soon as the child element is removed (which in the case of the virtual list happens when the element goes out of view). Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
As the virtual list removes its child elements that are no longer visible the index of the messages in the acceptance test is no longer an absolute index for the whole list, but an index only for the currently visible messages. However, as all the messages sent in each test fit in the available space no message is hidden and the previous indexes are still valid. The only change needed is in the locator for chat messages; as they are declared as direct children of their parent now they must be set as descendants of the wrapper instead of as descendants of the message list itself. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
danxuliu
force-pushed
the
improve-performance-of-the-list-of-chat-messages
branch
from
November 20, 2018 11:18
3d3a470
to
31e31a6
Compare
nickvergessen
approved these changes
Nov 20, 2018
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works 🐘
No backport on this one 🙊 |
nickvergessen
deleted the
improve-performance-of-the-list-of-chat-messages
branch
November 20, 2018 12:18
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
3. to review
bug
enhancement
feature: chat 💬
Chat and system messages
feature: frontend 🖌️
"Web UI" client
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Requires #1302
It seemed that this day was never going to come, but finally, here it is :-D
🎆 🎆 🎆 🎆 🎆 This pull request is ready for review!!! 🎆 🎆 🎆 🎆 🎆
Each time a new element or a group of elements is added to the document the browser needs to layout it (calculate its position and height), but if the elements use a static or relative positioning (they are part of the flow of the document) it needs to reflow the document too (layout other existing elements again that could be affected or affect the new elements).
When joining a room all the chat messages in the room were loaded and added one by one to the document. Each time a new message was added to the list all the other messages already in the list had to be layout again, and thus the document was reflown again and again each time with a higher number of elements.
Instead of adding each message on its own when several of them were received an initial improvement is to add them all at once. This way, instead of one reflow for each element only one reflow for the whole set is needed. However, this only helps up to some point; once the number of elements in the list is very high even a single reflow takes a lot of time, so not only the initial load, but also regular chat is sluggish.
The only way to improve the performance is thus limit the number of elements in the list. The virtual list does that while giving the user the impression that the list contains all the messages. How? By keeping in the document only the elements that are actually visible, removing those that are out of view, and updating them as needed when the user scrolls the list. For more information please refer to the documentation in the code itself ;-)
Of course, despite its benefits, this approach also comes with its own issues :-)
scrollTop()
returns the scroll position when the event was triggered instead of the current one (as done in the normal view of Chromium or in Firefox), so when the visible elements are updated the wrong ones end being shown. I do not know how that could be fixed, but it should not be a very big problem, as once the reload ends the scrolling shows the right elements, and again it actually looks like a bug in Chromium, and only in its responsive view.I have developed and tested the virtual list with the desktop versions of Firefox and Chromium; tests with the mobile versions are appreciated ;-) (And by the way, adding so much code without unit, integration or acceptance tests make me dizzy :-P ).
How to test (setup):
To notice the performance improvement it is necessary to use a room with a large number of messages. A naive way to get a room with a large number of messages would be to modify the
ChatController
to include the following code before$creationDateTime = new \DateTime('now', new \DateTimeZone('UTC'));
insendMessage
:With that code, whenever a new message is sent to the controller (simply typing in the UI, for example) 10000 extra messages would be added too to that room.
That code should be used only during the setup of the room; for the actual test of the virtual list it should be removed, as otherwise even with a virtual list sending a new message would take time.
How to test:
Result before this pull request:
Go for a walk while the messages are loaded when entering a room and hope that the browser has not crashed when you return; make some stretches whenever a new message is added or the window is resized; wait a little when scrolling the list
Result after this pull request:
Make some stretches while the messages are loaded when entering a room*; enjoy an immediate update whenever a new message is added, the window is resized or when scrolling the list
*When entering the room the browser becomes slightly unresponsive; this is mainly caused by the continuous load of messages by the model without any throttling, but this was not fixed because it will no longer happen once the messages are loaded on request in a later pull request.