Explorar el Código

Stop setting `_converse.windowState`

and rely on `document.hidden` instead.

Remove the `windowStateChanged` event.
JC Brand hace 1 año
padre
commit
686dd6ba63

+ 2 - 0
CHANGES.md

@@ -14,6 +14,8 @@
   providing access for 3rd party plugins to code (e.g. classes) from
   converse.js. Some classes that were on the `_converse` object, like
   `CustomElement` are not on `_converse.exports`.
+- The `windowStateChanged` event has been removed. If you used it, rely on the
+  `visibilitychange` event on `document` instead.
 
 ## 10.1.6 (2023-08-31)
 

+ 1 - 2
src/headless/plugins/chat/model.js

@@ -1080,8 +1080,7 @@ class ChatBox extends ModelWithContact {
      * @returns {boolean}
      */
     isHidden () {
-        // Note: This methods gets overridden by converse-minimize
-        return this.get('hidden') || this.isScrolledUp() || _converse.windowState === 'hidden';
+        return this.get('hidden') || this.isScrolledUp() || document.hidden;
     }
 
     /**

+ 3 - 2
src/headless/plugins/muc/index.js

@@ -15,7 +15,7 @@ import affiliations_api from './affiliations/api.js';
 import muc_api from './api.js';
 import _converse from '../../shared/_converse.js';
 import api, { converse } from '../../shared/api/index.js';
-import { CHATROOMS_TYPE } from '../..//shared/constants.js';
+import { CHATROOMS_TYPE } from '../../shared/constants.js';
 import {
     autoJoinRooms,
     disconnectChatRooms,
@@ -212,6 +212,7 @@ converse.plugins.add('converse-muc', {
         api.listen.on('chatBoxesFetched', autoJoinRooms);
         api.listen.on('disconnected', disconnectChatRooms);
         api.listen.on('statusInitialized', onStatusInitialized);
-        api.listen.on('windowStateChanged', onWindowStateChanged);
+
+        document.addEventListener('visibilitychange', onWindowStateChanged);
     },
 });

+ 2 - 2
src/headless/plugins/muc/utils.js

@@ -59,8 +59,8 @@ export function disconnectChatRooms () {
         .forEach(m => m.session.save({ 'connection_status': converse.ROOMSTATUS.DISCONNECTED }));
 }
 
-export async function onWindowStateChanged (data) {
-    if (data.state === 'visible' && api.connection.connected()) {
+export async function onWindowStateChanged () {
+    if (!document.hidden && api.connection.connected()) {
         const rooms = await api.rooms.get();
         rooms.forEach(room => room.rejoinIfNecessary());
     }

+ 2 - 1
src/headless/plugins/ping/index.js

@@ -27,6 +27,7 @@ converse.plugins.add('converse-ping', {
         api.listen.on('connected', registerHandlers);
         api.listen.on('reconnected', registerHandlers);
         api.listen.on('disconnected', unregisterIntervalHandler);
-        api.listen.on('windowStateChanged', onWindowStateChanged);
+
+        document.addEventListener('visibilitychange', onWindowStateChanged);
     }
 });

+ 2 - 2
src/headless/plugins/ping/utils.js

@@ -5,8 +5,8 @@ const { Strophe, $iq } = converse.env;
 
 let lastStanzaDate;
 
-export function onWindowStateChanged (data) {
-    data.state === 'visible' && api.ping(null, 5000);
+export function onWindowStateChanged () {
+    if (!document.hidden) api.ping(null, 5000);
 }
 
 export function setLastStanzaDate (date) {

+ 1 - 2
src/headless/utils/index.js

@@ -7,7 +7,7 @@ import log, { LEVELS } from '../log.js';
 import { Model } from '@converse/skeletor';
 import { toStanza } from 'strophe.js';
 import { getOpenPromise } from '@converse/openpromise';
-import { saveWindowState, shouldClearCache } from './session.js';
+import { shouldClearCache } from './session.js';
 import { merge, isError, isFunction } from './object.js';
 import { createStore, getDefaultStore } from './storage.js';
 import { waitUntil } from './promise.js';
@@ -226,7 +226,6 @@ export default Object.assign({
     queryChildren,
     replaceCurrentWord,
     safeSave,
-    saveWindowState,
     shouldClearCache,
     shouldCreateMessage,
     shouldRenderMediaFromURL,

+ 51 - 12
src/headless/utils/init.js

@@ -1,3 +1,6 @@
+/**
+ * @typedef {module:shared.converse.ConversePrivateGlobal} ConversePrivateGlobal
+ */
 import Storage from '@converse/skeletor/src/storage.js';
 import _converse from '../shared/_converse';
 import api from '../shared/api/index.js';
@@ -12,9 +15,15 @@ import { Strophe } from 'strophe.js';
 import { createStore, initStorage } from './storage.js';
 import { getConnectionServiceURL } from '../shared/connection/utils';
 import { isValidJID } from './jid.js';
-import { saveWindowState, isTestEnv } from './session.js';
+import { isTestEnv } from './session.js';
 
 
+/**
+ * Initializes the plugins for the Converse instance.
+ * @param {ConversePrivateGlobal} _converse
+ * @fires _converse#pluginsInitialized - Triggered once all plugins have been initialized.
+ * @memberOf _converse
+ */
 export function initPlugins (_converse) {
     // If initialize gets called a second time (e.g. during tests), then we
     // need to re-apply all plugins (for a new converse instance), and we
@@ -58,6 +67,9 @@ export function initPlugins (_converse) {
 }
 
 
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
 export async function initClientConfig (_converse) {
     /* The client config refers to configuration of the client which is
      * independent of any particular user.
@@ -81,6 +93,9 @@ export async function initClientConfig (_converse) {
 }
 
 
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
 export async function initSessionStorage (_converse) {
     await Storage.sessionStorageInitialized;
     _converse.storage = {
@@ -93,6 +108,11 @@ export async function initSessionStorage (_converse) {
 }
 
 
+/**
+ * Initializes persistent storage
+ * @param {ConversePrivateGlobal} _converse
+ * @param {string} store_name - The name of the store.
+ */
 function initPersistentStorage (_converse, store_name) {
     if (_converse.api.settings.get('persistent_store') === 'sessionStorage') {
         return;
@@ -126,6 +146,10 @@ function initPersistentStorage (_converse, store_name) {
 }
 
 
+/**
+ * @param {ConversePrivateGlobal} _converse
+ * @param {string} jid
+ */
 function saveJIDtoSession (_converse, jid) {
     jid = _converse.session.get('jid') || jid;
     if (_converse.api.settings.get("authentication") !== ANONYMOUS && !Strophe.getResourceFromJid(jid)) {
@@ -161,7 +185,7 @@ function saveJIDtoSession (_converse, jid) {
  * connection is set up.
  *
  * @emits _converse#setUserJID
- * @params { String } jid
+ * @param {string} jid
  */
 export async function setUserJID (jid) {
     await initSession(_converse, jid);
@@ -175,6 +199,10 @@ export async function setUserJID (jid) {
 }
 
 
+/**
+ * @param {ConversePrivateGlobal} _converse
+ * @param {string} jid
+ */
 export async function initSession (_converse, jid) {
     const is_shared_session = _converse.api.settings.get('connection_options').worker;
 
@@ -211,13 +239,12 @@ export async function initSession (_converse, jid) {
 }
 
 
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
 export function registerGlobalEventHandlers (_converse) {
-    document.addEventListener("visibilitychange", saveWindowState);
-    saveWindowState({'type': document.hidden ? "blur" : "focus"}); // Set initial state
     /**
-     * Called once Converse has registered its global event handlers
-     * (for events such as window resize or unload).
-     * Plugins can listen to this event as cue to register their own
+     * Plugins can listen to this event as cue to register their
      * global event handlers.
      * @event _converse#registeredGlobalEventHandlers
      * @example _converse.api.listen.on('registeredGlobalEventHandlers', () => { ... });
@@ -226,15 +253,19 @@ export function registerGlobalEventHandlers (_converse) {
 }
 
 
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
 function unregisterGlobalEventHandlers (_converse) {
-    const { api } = _converse;
-    document.removeEventListener("visibilitychange", saveWindowState);
-    api.trigger('unregisteredGlobalEventHandlers');
+    _converse.api.trigger('unregisteredGlobalEventHandlers');
 }
 
 
-// Make sure everything is reset in case this is a subsequent call to
-// converse.initialize (happens during tests).
+/**
+ * Make sure everything is reset in case this is a subsequent call to
+ * converse.initialize (happens during tests).
+ * @param {ConversePrivateGlobal} _converse
+ */
 export async function cleanup (_converse) {
     const { api } = _converse;
     await api.trigger('cleanup', {'synchronous': true});
@@ -248,6 +279,14 @@ export async function cleanup (_converse) {
 }
 
 
+/**
+ * Fetches login credentials from the server.
+ * @param {number} [wait=0]
+ *  The time to wait and debounce subsequent calls to this function before making the request.
+ * @returns {Promise<{jid: string, password: string}>}
+ *  A promise that resolves with the provided login credentials (JID and password).
+ * @throws {Error} If the request fails or returns an error status.
+ */
 function fetchLoginCredentials (wait=0) {
     return new Promise(
         debounce(async (resolve, reject) => {

+ 0 - 31
src/headless/utils/session.js

@@ -18,37 +18,6 @@ export function isTestEnv () {
     return getInitSettings()['bosh_service_url'] === 'montague.lit/http-bind';
 }
 
-export function saveWindowState (ev) {
-    // XXX: eventually we should be able to just use
-    // document.visibilityState (when we drop support for older
-    // browsers).
-    let state;
-    const event_map = {
-        'focus': "visible",
-        'focusin': "visible",
-        'pageshow': "visible",
-        'blur': "hidden",
-        'focusout': "hidden",
-        'pagehide': "hidden"
-    };
-    ev = ev || document.createEvent('Events');
-    if (ev.type in event_map) {
-        state = event_map[ev.type];
-    } else {
-        state = document.hidden ? "hidden" : "visible";
-    }
-    _converse.windowState = state;
-    /**
-     * Triggered when window state has changed.
-     * Used to determine when a user left the page and when came back.
-     * @event _converse#windowStateChanged
-     * @type { object }
-     * @property{ string } state - Either "hidden" or "visible"
-     * @example _converse.api.listen.on('windowStateChanged', obj => { ... });
-     */
-    _converse.api.trigger('windowStateChanged', {state});
-}
-
 export function setUnloadEvent () {
     if ('onpagehide' in window) {
         // Pagehide gets thrown in more cases than unload. Specifically it

+ 3 - 2
src/plugins/chatview/chat.js

@@ -8,7 +8,7 @@ import { _converse, api } from '@converse/headless';
 /**
  * The view of an open/ongoing chat conversation.
  * @class
- * @namespace _converse.ChatBoxView
+ * @namespace _converse.ChatView
  * @memberOf _converse
  */
 export default class ChatView extends BaseChatView {
@@ -17,10 +17,11 @@ export default class ChatView extends BaseChatView {
     async initialize () {
         _converse.chatboxviews.add(this.jid, this);
         this.model = _converse.chatboxes.get(this.jid);
-        this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
         this.listenTo(this.model, 'change:hidden', () => !this.model.get('hidden') && this.afterShown());
         this.listenTo(this.model, 'change:show_help_messages', () => this.requestUpdate());
 
+        document.addEventListener('visibilitychange',  () => this.onWindowStateChanged());
+
         await this.model.messages.fetched;
         !this.model.get('hidden') && this.afterShown()
         /**

+ 1 - 1
src/plugins/chatview/tests/messages.js

@@ -17,7 +17,7 @@ describe("A Chat Message", function () {
         await u.waitUntil(() => view.querySelector('converse-chat-message .chat-msg__text')?.textContent === 'This message will be read');
         expect(view.model.get('num_unread')).toBe(0);
 
-        _converse.windowState = 'hidden';
+        spyOn(view.model, 'isHidden').and.returnValue(true);
         await _converse.handleMessageStanza(mock.createChatMessage(_converse, contact_jid, 'This message will be new'));
 
         await u.waitUntil(() => view.model.messages.length);

+ 5 - 6
src/plugins/chatview/tests/unreads.js

@@ -56,7 +56,7 @@ describe("A ChatBox's Unread Message Count", function () {
         const sent_stanzas = [];
         spyOn(api.connection.get(), 'send').and.callFake(s => sent_stanzas.push(s?.nodeTree ?? s));
         const chatbox = _converse.chatboxes.get(sender_jid);
-        _converse.windowState = 'hidden';
+        spyOn(chatbox, 'isHidden').and.returnValue(true);
         const msg = msgFactory();
         _converse.handleMessageStanza(msg);
         await u.waitUntil(() => chatbox.messages.length);
@@ -79,7 +79,6 @@ describe("A ChatBox's Unread Message Count", function () {
         spyOn(api.connection.get(), 'send').and.callFake(s => sent_stanzas.push(s?.nodeTree ?? s));
         const chatbox = _converse.chatboxes.get(sender_jid);
         chatbox.ui.set('scrolled', true);
-        _converse.windowState = 'hidden';
         const msg = msgFactory();
         _converse.handleMessageStanza(msg);
         await u.waitUntil(() => chatbox.messages.length);
@@ -101,7 +100,7 @@ describe("A ChatBox's Unread Message Count", function () {
         const sent_stanzas = [];
         spyOn(api.connection.get(), 'send').and.callFake(s => sent_stanzas.push(s?.nodeTree ?? s));
         const chatbox = _converse.chatboxes.get(sender_jid);
-        _converse.windowState = 'hidden';
+        spyOn(chatbox, 'isHidden').and.returnValue(true);
         const msg = msgFactory();
         _converse.handleMessageStanza(msg);
         await u.waitUntil(() => chatbox.messages.length);
@@ -110,7 +109,8 @@ describe("A ChatBox's Unread Message Count", function () {
         expect(chatbox.get('first_unread_id')).toBe(msgid);
         await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'message').length === 1);
         expect(sent_stanzas[0].querySelector('received')).toBeDefined();
-        u.saveWindowState({'type': 'focus'});
+        chatbox.isHidden.and.returnValue(false);
+        document.dispatchEvent(new Event('visibilitychange'));
         await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'message').length === 2);
         expect(sent_stanzas[1].querySelector('displayed')).toBeDefined();
         expect(chatbox.get('num_unread')).toBe(0);
@@ -145,7 +145,6 @@ describe("A ChatBox's Unread Message Count", function () {
         spyOn(api.connection.get(), 'send').and.callFake(s => sent_stanzas.push(s?.nodeTree ?? s));
         const chatbox = _converse.chatboxes.get(sender_jid);
         chatbox.ui.set('scrolled', true);
-        _converse.windowState = 'hidden';
         const msg = msgFactory();
         _converse.handleMessageStanza(msg);
         await u.waitUntil(() => chatbox.messages.length);
@@ -154,7 +153,7 @@ describe("A ChatBox's Unread Message Count", function () {
         expect(chatbox.get('first_unread_id')).toBe(msgid);
         await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'message').length === 1);
         expect(sent_stanzas[0].querySelector('received')).toBeDefined();
-        u.saveWindowState({'type': 'focus'});
+        document.dispatchEvent(new Event('visibilitychange'));
         await u.waitUntil(() => chatbox.get('num_unread') === 1);
         expect(chatbox.get('first_unread_id')).toBe(msgid);
         expect(sent_stanzas[0].querySelector('received')).toBeDefined();

+ 7 - 3
src/plugins/headlines-view/view.js

@@ -10,19 +10,20 @@ class HeadlinesFeedView extends BaseChatView {
 
         this.model = _converse.chatboxes.get(this.jid);
         this.model.disable_mam = true; // Don't do MAM queries for this box
-        this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
         this.listenTo(this.model, 'change:hidden', () => this.afterShown());
         this.listenTo(this.model, 'destroy', this.remove);
         this.listenTo(this.model.messages, 'add', () => this.requestUpdate());
         this.listenTo(this.model.messages, 'remove', () => this.requestUpdate());
         this.listenTo(this.model.messages, 'reset', () => this.requestUpdate());
 
+        document.addEventListener('visibilitychange',  () => this.onWindowStateChanged());
+
         await this.model.messages.fetched;
         this.model.maybeShow();
         /**
-         * Triggered once the { @link _converse.HeadlinesFeedView } has been initialized
+         * Triggered once the {@link HeadlinesFeedView} has been initialized
          * @event _converse#headlinesBoxViewInitialized
-         * @type { _converse.HeadlinesFeedView }
+         * @type {HeadlinesFeedView}
          * @example _converse.api.listen.on('headlinesBoxViewInitialized', view => { ... });
          */
         api.trigger('headlinesBoxViewInitialized', this);
@@ -32,6 +33,9 @@ class HeadlinesFeedView extends BaseChatView {
         return tplHeadlines(this.model);
     }
 
+    /**
+     * @param {Event} ev
+     */
     async close (ev) {
         ev?.preventDefault?.();
         if (location.hash === 'converse/chat?jid=' + this.model.get('jid')) {

+ 4 - 4
src/plugins/muc-views/muc.js

@@ -12,17 +12,17 @@ export default class MUCView extends BaseChatView {
         _converse.chatboxviews.add(this.jid, this);
         this.setAttribute('id', this.model.get('box_id'));
 
-        this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
-        this.listenTo(this.model, 'change:composing_spoiler', this.requestUpdateMessageForm);
         this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
         this.listenTo(this.model.session, 'change:view', () => this.requestUpdate());
 
+        document.addEventListener('visibilitychange',  () => this.onWindowStateChanged());
+
         this.onConnectionStatusChanged();
         this.model.maybeShow();
         /**
-         * Triggered once a {@link _converse.ChatRoomView} has been opened
+         * Triggered once a {@link MUCView} has been opened
          * @event _converse#chatRoomViewInitialized
-         * @type { _converse.ChatRoomView }
+         * @type {MUCView}
          * @example _converse.api.listen.on('chatRoomViewInitialized', view => { ... });
          */
         api.trigger('chatRoomViewInitialized', this);

+ 12 - 11
src/plugins/notifications/tests/notification.js

@@ -204,7 +204,9 @@ describe("Notifications", function () {
             spyOn(converse.env, 'Favico').and.returnValue(favico);
 
             const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-            const previous_state = _converse.windowState;
+            const view = await mock.openChatBoxFor(_converse, sender_jid)
+            spyOn(view.model, 'isHidden').and.returnValue(true);
+
             const msg = $msg({
                     from: sender_jid,
                     to: _converse.api.connection.get().jid,
@@ -212,7 +214,6 @@ describe("Notifications", function () {
                     id: u.getUniqueId()
                 }).c('body').t('This message will increment the message counter').up()
                   .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
-            _converse.windowState = 'hidden';
 
             spyOn(_converse.api, "trigger").and.callThrough();
 
@@ -234,17 +235,16 @@ describe("Notifications", function () {
             await u.waitUntil(() => favico.badge.calls.count() === 1);
             expect(favico.badge.calls.mostRecent().args.pop()).toBe(2);
 
-            const view = _converse.chatboxviews.get(sender_jid);
             expect(view.model.get('num_unread')).toBe(2);
 
             // Check that it's cleared when the window is focused
-            _converse.windowState = 'hidden';
-            u.saveWindowState({'type': 'focus'});
+            view.model.isHidden.and.returnValue(false);
+            document.dispatchEvent(new Event('visibilitychange'));
+
             await u.waitUntil(() => favico.badge.calls.count() === 2);
             expect(favico.badge.calls.mostRecent().args.pop()).toBe(0);
 
             expect(view.model.get('num_unread')).toBe(0);
-            _converse.windowSate = previous_state;
         }));
 
         it("is not incremented when the message is received and the window is focused",
@@ -256,7 +256,7 @@ describe("Notifications", function () {
             const favico = jasmine.createSpyObj('favico', ['badge']);
             spyOn(converse.env, 'Favico').and.returnValue(favico);
 
-            u.saveWindowState({'type': 'focus'});
+            document.dispatchEvent(new Event('visibilitychange'));
             const message = 'This message will not increment the message counter';
             const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
                 msg = $msg({
@@ -297,16 +297,17 @@ describe("Notifications", function () {
                 .tree();
 
             // leave converse-chat page
-            _converse.windowState = 'hidden';
+            spyOn(_converse.exports.ChatBox.prototype, 'isHidden').and.returnValue(true);
+
             await _converse.handleMessageStanza(msgFactory());
             let view = _converse.chatboxviews.get(sender_jid);
             await u.waitUntil(() => favico.badge.calls.count() === 1, 1000);
             expect(favico.badge.calls.mostRecent().args.pop()).toBe(1);
             expect(view.model.get('num_unread')).toBe(1);
 
+            view.model.isHidden.and.returnValue(false);
             // come back to converse-chat page
-            u.saveWindowState({'type': 'focus'});
-
+            document.dispatchEvent(new Event('visibilitychange'));
 
             await u.waitUntil(() => u.isVisible(view));
             expect(view.model.get('num_unread')).toBe(0);
@@ -316,7 +317,7 @@ describe("Notifications", function () {
 
             // close chatbox and leave converse-chat page again
             view.close();
-            _converse.windowState = 'hidden';
+            view.model.isHidden.and.returnValue(true);
 
             // check that msg_counter is incremented from zero again
             await _converse.handleMessageStanza(msgFactory());

+ 17 - 8
src/shared/chat/baseview.js

@@ -1,7 +1,10 @@
+/**
+ * @typedef {import('@converse/skeletor').Model} Model
+ */
 import { CustomElement } from '../components/element.js';
 import { _converse, api } from '@converse/headless';
 import { onScrolledDown } from './utils.js';
-import { CHATROOMS_TYPE } from '@converse/headless/shared/constants.js';
+import { CHATROOMS_TYPE, INACTIVE } from '@converse/headless/shared/constants.js';
 
 
 export default class BaseChatView extends CustomElement {
@@ -12,6 +15,12 @@ export default class BaseChatView extends CustomElement {
         }
     }
 
+    constructor () {
+        super();
+        this.jid = /** @type {string} */ null;
+        this.model = /** @type {Model} */ null;
+    }
+
     disconnectedCallback () {
         super.disconnectedCallback();
         _converse.chatboxviews.remove(this.jid, this);
@@ -52,7 +61,7 @@ export default class BaseChatView extends CustomElement {
         /**
          * Triggered when the focus has been removed from a particular chat.
          * @event _converse#chatBoxBlurred
-         * @type { _converse.ChatBoxView | _converse.ChatRoomView }
+         * @type {BaseChatView}
          * @example _converse.api.listen.on('chatBoxBlurred', (view, event) => { ... });
          */
         api.trigger('chatBoxBlurred', this, ev);
@@ -66,7 +75,7 @@ export default class BaseChatView extends CustomElement {
         /**
          * Triggered when the focus has been moved to a particular chat.
          * @event _converse#chatBoxFocused
-         * @type { _converse.ChatBoxView | _converse.ChatRoomView }
+         * @type {BaseChatView}
          * @example _converse.api.listen.on('chatBoxFocused', (view, event) => { ... });
          */
         api.trigger('chatBoxFocused', this, ev);
@@ -104,14 +113,14 @@ export default class BaseChatView extends CustomElement {
         onScrolledDown(this.model);
     }
 
-    onWindowStateChanged (data) {
-        if (data.state === 'visible') {
+    onWindowStateChanged () {
+        if (document.hidden) {
+            this.model.setChatState(INACTIVE, { 'silent': true });
+            this.model.sendChatState();
+        } else {
             if (!this.model.isHidden()) {
                 this.model.clearUnreadMsgCounter();
             }
-        } else if (data.state === 'hidden') {
-            this.model.setChatState(_converse.INACTIVE, { 'silent': true });
-            this.model.sendChatState();
         }
     }
 }