Przeglądaj źródła

Some work on profiling message insertions and trying to optimize

JC Brand 6 lat temu
rodzic
commit
32b9244615
6 zmienionych plików z 108 dodań i 73 usunięć
  1. 37 1
      spec/profiling.js
  2. 39 40
      src/converse-chatview.js
  3. 1 1
      src/templates/message.html
  4. 1 1
      src/utils/html.js
  5. 1 1
      tests/mock.js
  6. 29 29
      tests/runner.js

+ 37 - 1
spec/profiling.js

@@ -4,11 +4,47 @@
     var _ = converse.env._;
     var $iq = converse.env.$iq;
     var $pres = converse.env.$pres;
+    var $msg = converse.env.$msg;
     var u = converse.env.utils;
 
     describe("Profiling", function() {
 
-        it("shows users currently present in the groupchat",
+        it("loads lots of messages in a chat",
+            mock.initConverse(
+                null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
+                async function (done, _converse) {
+
+            await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
+            const view = _converse.api.chatviews.get('lounge@montague.lit');
+            const chat_content = view.el.querySelector('.chat-content');
+            await test_utils.waitUntil(() => chat_content.querySelector('.message'));
+            await test_utils.waitUntil(() => u.isVisible(chat_content.querySelector('.message')));
+            await test_utils.waitUntil(() => !view.el.querySelector('.spinner'));
+
+            const nick = mock.chatroom_names[0];
+            const message = `First Post!`;
+            const msg = $msg({
+                    from: 'lounge@montague.lit/'+nick,
+                    id: u.getUniqueId(),
+                    to: 'romeo@montague.lit',
+                    type: 'groupchat'
+                }).c('body').t(message).tree();
+            await view.model.onMessage(msg);
+            await new Promise((resolve, reject) => view.once('messageInserted', resolve));
+            _.range(0, 3000).forEach(i => {
+                const message = `Message ${i.toString().padStart(5, '0')}`;
+                const msg = $msg({
+                        from: 'lounge@montague.lit/'+nick,
+                        id: u.getUniqueId(),
+                        to: 'romeo@montague.lit',
+                        type: 'groupchat'
+                    }).c('body').t(message).tree();
+                view.model.onMessage(msg);
+            });
+            done();
+        }));
+
+        xit("shows users currently present in the groupchat",
             mock.initConverse(
                 null, ['rosterGroupsFetched'], {'muc_show_join_leave': false},
                 async function (done, _converse) {

+ 39 - 40
src/converse-chatview.js

@@ -370,6 +370,7 @@ converse.plugins.add('converse-chatview', {
                 this.scrollDown = _.debounce(this._scrollDown, 100);
                 this.markScrolled = _.debounce(this._markScrolled, 100);
                 this.show = _.debounce(this._show, 500, {'leading': true});
+                this.showMessages = _.debounce(this._showMessages, 100);
             },
 
             render () {
@@ -682,7 +683,6 @@ converse.plugins.add('converse-chatview', {
                         // should maintain our current scroll position.
                         if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) {
                             const top_visible_message = this.model.get('top_visible_message') || next_msg_el;
-
                             this.model.set('top_visible_message', top_visible_message);
                             this.content.scrollTop = top_visible_message.offsetTop - 30;
                         }
@@ -716,13 +716,19 @@ converse.plugins.add('converse-chatview', {
             },
 
             /**
-             * Given a view representing a message, insert it into the
-             * content area of the chat box.
+             * Given a {@link _converse.Message} instance, insert it into the content area of the
+             * {@link _converse.ChatBoxView} or {@link _converse.ChatRoomView}.
+             *
              * @private
              * @method _converse.ChatBoxView#insertMessage
-             * @param { Backbone.View } message - The message Backbone.View
+             * @param { _converse.Message } message
              */
-            insertMessage (view) {
+            async insertMessage (message) {
+                // Clear chat state notifications
+                sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content).forEach(u.removeElement);
+
+                const view = this.add(message.get('id'), new _converse.MessageView({'model': message}));
+                await view.render();
                 if (view.model.get('type') === 'error') {
                     const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
                     if (previous_msg_el) {
@@ -730,9 +736,8 @@ converse.plugins.add('converse-chatview', {
                         return this.trigger('messageInserted', view.el);
                     }
                 }
-                const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date(),
-                      previous_msg_date = this.getLastMessageDate(current_msg_date);
-
+                const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date();
+                const previous_msg_date = this.getLastMessageDate(current_msg_date);
                 if (_.isNull(previous_msg_date)) {
                     this.content.insertAdjacentElement('afterbegin', view.el);
                 } else {
@@ -746,9 +751,27 @@ converse.plugins.add('converse-chatview', {
                     previous_msg_el.insertAdjacentElement('afterend', view.el);
                     this.markFollowups(view.el);
                 }
-                return this.trigger('messageInserted', view.el);
+
+                if (u.isNewMessage(message)) {
+                    if (message.get('sender') === 'me') {
+                        // We remove the "scrolled" flag so that the chat area
+                        // gets scrolled down. We always want to scroll down
+                        // when the user writes a message as opposed to when a
+                        // message is received.
+                        this.model.set('scrolled', false);
+                    } else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) {
+                        this.showNewMessagesIndicator();
+                    }
+                }
+                this.insertDayIndicator(view.el);
+                this.setScrollPosition(view.el);
+                if (message.get('correcting')) {
+                    this.insertIntoTextArea(message.get('message'), true, true);
+                }
+                this.trigger('messageInserted', view.el);
             },
 
+
             /**
              * Given a message element, determine wether it should be
              * marked as a followup message to the previous element.
@@ -788,45 +811,20 @@ converse.plugins.add('converse-chatview', {
                 }
             },
 
-            /**
-             * Inserts a chat message into the content area of the chat box.
-             * Will also insert a new day indicator if the message is on a different day.
-             * @private
-             * @method _converse.ChatBoxView#showMessage
-             * @param { _converse.Message } message - The message object
-             */
-            async showMessage (message) {
-                const view = this.add(message.get('id'), new _converse.MessageView({'model': message}));
-                await view.render();
 
-                // Clear chat state notifications
-                sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content).forEach(u.removeElement);
+            _showMessages () {
+                // This method gets debounced, so it only gets executed once
+                // per batch of messages.
+                Object.values(this.getAll()).forEach(view => u.removeClass('hidden', view.el));
 
-                this.insertMessage(view);
-                this.insertDayIndicator(view.el);
-                this.setScrollPosition(view.el);
-
-                if (u.isNewMessage(message)) {
-                    if (message.get('sender') === 'me') {
-                        // We remove the "scrolled" flag so that the chat area
-                        // gets scrolled down. We always want to scroll down
-                        // when the user writes a message as opposed to when a
-                        // message is received.
-                        this.model.set('scrolled', false);
-                    } else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) {
-                        this.showNewMessagesIndicator();
-                    }
-                }
                 if (this.shouldShowOnTextMessage()) {
                     this.show();
                 } else {
                     this.scrollDown();
                 }
-                if (message.get('correcting')) {
-                    this.insertIntoTextArea(message.get('message'), true, true);
-                }
             },
 
+
             /**
              * Handler that gets called when a new message object is created.
              * @private
@@ -843,7 +841,8 @@ converse.plugins.add('converse-chatview', {
                     // Ignore archived or delayed messages without any text to show.
                     return message.destroy();
                 }
-                await this.showMessage(message);
+                await this.insertMessage(message);
+                this.showMessages();
                 /**
                  * Triggered once a message has been added to a chatbox.
                  * @event _converse#messageAdded

+ 1 - 1
src/templates/message.html

@@ -1,4 +1,4 @@
-<div class="message chat-msg {{{o.type}}} {[ if (o.is_me_message) { ]} chat-msg--action {[ } ]} {{{o.extra_classes}}}"
+<div class="hidden message chat-msg {{{o.type}}} {[ if (o.is_me_message) { ]} chat-msg--action {[ } ]} {{{o.extra_classes}}}"
         data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}" data-encrypted="{{{o.is_encrypted}}}">
     {[ if (o.type !== 'headline' && !o.is_me_message) { ]}
     <canvas class="avatar chat-msg__avatar" height="36" width="36"></canvas>

+ 1 - 1
src/utils/html.js

@@ -268,7 +268,7 @@ u.addClass = function (className, el) {
 }
 
 u.removeClass = function (className, el) {
-    if (el instanceof Element) {
+    if (el instanceof Element && u.hasClass(className, el)) {
         el.classList.remove(className);
     }
     return el;

+ 1 - 1
tests/mock.js

@@ -255,7 +255,7 @@
         return async done => {
             const _converse = await initConverse(settings, spies);
             function _done () {
-                _converse.api.user.logout();
+                // _converse.api.user.logout();
                 done();
             }
             const promises = _.map(promise_names, _converse.api.waitUntil);

+ 29 - 29
tests/runner.js

@@ -33,36 +33,36 @@ require.config(config);
 var specs = [
     "jasmine",
     //"spec/transcripts",
-    "spec/spoilers",
-    "spec/roomslist",
     "spec/profiling",
-    "spec/utils",
-    "spec/converse",
-    "spec/bookmarks",
-    "spec/headline",
-    "spec/disco",
-    "spec/protocol",
-    "spec/presence",
-    "spec/eventemitter",
-    "spec/smacks",
-    "spec/ping",
-    "spec/push",
-    "spec/xmppstatus",
-    "spec/mam",
-    "spec/omemo",
-    "spec/controlbox",
-    "spec/roster",
-    "spec/chatbox",
-    "spec/user-details-modal",
-    "spec/messages",
-    "spec/muc",
-    "spec/room_registration",
-    "spec/autocomplete",
-    "spec/minchats",
-    "spec/notification",
-    "spec/login",
-    "spec/register",
-    "spec/http-file-upload"
+    // "spec/spoilers",
+    // "spec/roomslist",
+    // "spec/utils",
+    // "spec/converse",
+    // "spec/bookmarks",
+    // "spec/headline",
+    // "spec/disco",
+    // "spec/protocol",
+    // "spec/presence",
+    // "spec/eventemitter",
+    // "spec/smacks",
+    // "spec/ping",
+    // "spec/push",
+    // "spec/xmppstatus",
+    // "spec/mam",
+    // "spec/omemo",
+    // "spec/controlbox",
+    // "spec/roster",
+    // "spec/chatbox",
+    // "spec/user-details-modal",
+    // "spec/messages",
+    // "spec/muc",
+    // "spec/room_registration",
+    // "spec/autocomplete",
+    // "spec/minchats",
+    // "spec/notification",
+    // "spec/login",
+    // "spec/register",
+    // "spec/http-file-upload"
 ];
 
 require(['console-reporter', 'mock', 'sinon', 'wait-until-promise'], (ConsoleReporter, mock, sinon, waitUntilPromise) => {