Skip to content

Commit

Permalink
Stop setting _converse.windowState
Browse files Browse the repository at this point in the history
and rely on `document.hidden` instead.

Remove the `windowStateChanged` event.
  • Loading branch information
jcbrand committed Nov 14, 2023
1 parent 33ce4fc commit e2c81c0
Show file tree
Hide file tree
Showing 16 changed files with 113 additions and 89 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 1 addition & 2 deletions src/headless/plugins/chat/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -1082,8 +1082,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;
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/headless/plugins/muc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
},
});
4 changes: 2 additions & 2 deletions src/headless/plugins/muc/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
3 changes: 2 additions & 1 deletion src/headless/plugins/ping/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
4 changes: 2 additions & 2 deletions src/headless/plugins/ping/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 1 addition & 2 deletions src/headless/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -226,7 +226,6 @@ export default Object.assign({
queryChildren,
replaceCurrentWord,
safeSave,
saveWindowState,
shouldClearCache,
shouldCreateMessage,
shouldRenderMediaFromURL,
Expand Down
63 changes: 51 additions & 12 deletions src/headless/utils/init.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -81,6 +93,9 @@ export async function initClientConfig (_converse) {
}


/**
* @param {ConversePrivateGlobal} _converse
*/
export async function initSessionStorage (_converse) {
await Storage.sessionStorageInitialized;
_converse.storage = {
Expand All @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand Down Expand Up @@ -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', () => { ... });
Expand All @@ -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});
Expand All @@ -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) => {
Expand Down
31 changes: 0 additions & 31 deletions src/headless/utils/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/chatview/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
/**
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/chatview/tests/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 5 additions & 6 deletions src/plugins/chatview/tests/unreads.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand Down
10 changes: 7 additions & 3 deletions src/plugins/headlines-view/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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')) {
Expand Down
Loading

0 comments on commit e2c81c0

Please sign in to comment.