diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index 7356291391..667af80170 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -76,11 +76,15 @@ export class HomeAssistantAppEl extends HassElement { // @ts-ignore this._loadHassTranslations(this.hass!.language, "state"); + this.addEventListener("unsuspend-app", () => this._onVisible(), false); + document.addEventListener( "visibilitychange", - () => this._handleVisibilityChange(), + () => this._checkVisibility(), false ); + document.addEventListener("freeze", () => this._suspendApp()); + document.addEventListener("resume", () => this._checkVisibility()); } protected hassReconnected() { @@ -148,30 +152,53 @@ export class HomeAssistantAppEl extends HassElement { : route.path.substr(1, dividerPos - 1); } - protected _handleVisibilityChange() { + protected _checkVisibility() { if (document.hidden) { // If the document is hidden, we will prevent reconnects until we are visible again - this.hass!.connection.suspendReconnectUntil( - new Promise((resolve) => { - this._visiblePromiseResolve = resolve; - }) - ); - // We close the connection to Home Assistant after being hidden for 5 minutes - this._hiddenTimeout = window.setTimeout(() => { - this._hiddenTimeout = undefined; - this.hass!.connection.suspend(); - }, 300000); + this._onHidden(); } else { - // Clear timer to close the connection - if (this._hiddenTimeout) { - clearTimeout(this._hiddenTimeout); - this._hiddenTimeout = undefined; - } - // Unsuspend the reconnect - if (this._visiblePromiseResolve) { - this._visiblePromiseResolve(); - this._visiblePromiseResolve = undefined; + this._onVisible(); + } + } + + private _onHidden() { + if (this._visiblePromiseResolve) { + return; + } + this.hass!.connection.suspendReconnectUntil( + new Promise((resolve) => { + this._visiblePromiseResolve = resolve; + }) + ); + // We close the connection to Home Assistant after being hidden for 5 minutes + this._hiddenTimeout = window.setTimeout(() => { + this._hiddenTimeout = undefined; + // setTimeout can be delayed in the background and only fire + // when we switch to the tab or app again (Hey Android!) + if (!document.hidden) { + this._suspendApp(); } + }, 300000); + window.addEventListener("focus", () => this._onVisible(), { once: true }); + } + + private _suspendApp() { + if (!this.hass!.connection.connected) { + return; + } + this.hass!.connection.suspend(); + } + + private _onVisible() { + // Clear timer to close the connection + if (this._hiddenTimeout) { + clearTimeout(this._hiddenTimeout); + this._hiddenTimeout = undefined; + } + // Unsuspend the reconnect + if (this._visiblePromiseResolve) { + this._visiblePromiseResolve(); + this._visiblePromiseResolve = undefined; } } } diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts index e17e41d8ed..a3cb084c36 100644 --- a/src/layouts/partial-panel-resolver.ts +++ b/src/layouts/partial-panel-resolver.ts @@ -99,11 +99,13 @@ class PartialPanelResolver extends HassRouterPage { protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); + // Attach listeners for visibility document.addEventListener( "visibilitychange", - () => this._handleVisibilityChange(), + () => this._checkVisibility(), false ); + document.addEventListener("resume", () => this._checkVisibility()); } protected updated(changedProps: PropertyValues) { @@ -156,34 +158,48 @@ class PartialPanelResolver extends HassRouterPage { } } - private _handleVisibilityChange() { + private _checkVisibility() { if (document.hidden) { - this._hiddenTimeout = window.setTimeout(() => { - this._hiddenTimeout = undefined; - const curPanel = this.hass.panels[this._currentPage]; - if ( - this.lastChild && - // iFrames will lose their state when disconnected - // Do not disconnect any iframe panel - curPanel.component_name !== "iframe" && - // Do not disconnect any custom panel that embeds into iframe (ie hassio) - (curPanel.component_name !== "custom" || - !(curPanel.config as CustomPanelInfo).config._panel_custom - .embed_iframe) - ) { - this._disconnectedPanel = this.lastChild; - this.removeChild(this.lastChild); - } - }, 300000); + this._onHidden(); } else { - if (this._hiddenTimeout) { - clearTimeout(this._hiddenTimeout); - this._hiddenTimeout = undefined; + this._onVisible(); + } + } + + private _onHidden() { + this._hiddenTimeout = window.setTimeout(() => { + this._hiddenTimeout = undefined; + // setTimeout can be delayed in the background and only fire + // when we switch to the tab or app again (Hey Android!) + if (!document.hidden) { + return; } - if (this._disconnectedPanel) { - this.appendChild(this._disconnectedPanel); - this._disconnectedPanel = undefined; + const curPanel = this.hass.panels[this._currentPage]; + if ( + this.lastChild && + // iFrames will lose their state when disconnected + // Do not disconnect any iframe panel + curPanel.component_name !== "iframe" && + // Do not disconnect any custom panel that embeds into iframe (ie hassio) + (curPanel.component_name !== "custom" || + !(curPanel.config as CustomPanelInfo).config._panel_custom + .embed_iframe) + ) { + this._disconnectedPanel = this.lastChild; + this.removeChild(this.lastChild); } + }, 300000); + window.addEventListener("focus", () => this._onVisible(), { once: true }); + } + + private _onVisible() { + if (this._hiddenTimeout) { + clearTimeout(this._hiddenTimeout); + this._hiddenTimeout = undefined; + } + if (this._disconnectedPanel) { + this.appendChild(this._disconnectedPanel); + this._disconnectedPanel = undefined; } }