Kilka okien w telefonie

0

Niedawno utworzyłem pewną aplikację HTML+JS+WASM, która otwiera osobne okna z elementami interfejsu. Aby łatwiej było zrozumieć, główny plik to będzie "rodzic", a plik otwierany w osobnym oknie, to będzie "dziecko". Architektonicznie, rodzic co pół sekundy uruchamia pewną funkcję w WASM, która wywołuje funkcję JavaScript, a owa funkcja we wszystkich dzieciach uruchamia określoną funkcję. Cykliczność jest zrealizowana przez setTimeout.

Na komputerze (przegladarka Firefox i Chrome) działa to prawidłowo, tak, jak chciałem i wydawało się po temacie, nie ma znaczenia, czy dane okno jest zminimalizowane, zmaksymalizowane, widoczne, ukryte.

Okazało się, że na telefonie z Androidem, w Chrome działa to problematycznie. Jeżeli na wierzchu jest rodzic, a pod spotem okno z dzieckiem, to działa dobrze, ale jeżeli okno dziecka jest na wierzchu, a rodzic pod spotem, to raz nie uruchamia się, raz uruchamia się, ale rzadziej. Jedynie, jeżeli rodzic uruchamia Worker i ten Worker wysyła wiadomość zyklicznie, to działa prawidłowo, ale nie zawsze apka działa z workerem.

Zrobiłem do testów samo setTimeout, żeby to sprawdzić na telefonie, po obu stronach jest funkcja, która zwiększa licznik u siebie i po drugiej stronie, czyli testuję setTimeout i u rodzica i u dziecka. Na komputerze tak samo działa dobrze, czyli niezależnie od tego, jak ułożę okna, oba liczniki "biją" równomiernie, co ok. pół sekundy. Natomiast na telefonie, to już nie za bardzo. Licznik wywoływany przez skrypt pod spodem bije licznik wolniej, a będący aktualnie na wierzchu bije zgodnie z zamiarem.

Plik index.html, czyli rodzic

<!doctype html>
<html lang="en-us">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Rodzic</title>
</head>
<body>

<input type="button" onclick="OpenChild()" value="Otwórz okno">
<input type="button" onclick="ChildTestWindow()" value="Start">


<br>
To jest rodzic<br>
Rodzic:<span id="counter1">0</span><br>
Dziecko:<span id="counter2">0</span><br>

<script type='text/javascript'>
    let ChildObjWin = null;

    function OpenChild()
    {
        const WinX = 0;
        const WinY = 0;
        const WinW = 640;
        const WinH = 480;
        ChildObjWin = window.open("child.html", "_blank", "left=" + WinX + "px,top=" + WinY + "px,width=" + WinW + "px,height=" + WinH + "px");
    }



    function ChildTestWindow()
    {
        const T = document.getElementById("counter1").innerHTML;
        document.getElementById("counter1").innerHTML = parseInt(T) + 1;
        if (ChildObjWin)
        {
            ChildObjWin.TestFunc();
        }
        
        setTimeout(ChildTestWindow, 500);
    }
    
    function FuncFromChildToParent()
    {
        const T = document.getElementById("counter2").innerHTML;
        document.getElementById("counter2").innerHTML = parseInt(T) + 1;
    }
</script>

</body>
</html>

Plik child.html, czyli dziecko

<!doctype html>
<html lang="en-us">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Dziecko</title>
</head>
<body>
<input type="button" onclick="ParentTest()" value="Start">
<br>

To jest dziecko<br>
Rodzic:<span id="counter1">0</span><br>
Dziecko:<span id="counter2">0</span><br>

<script type='text/javascript'>

    function PrintInfo(Msg)
    {
        document.getElementById("info").innerHTML = document.getElementById("info").innerHTML + Msg + "<br>";
    }

    function TestFunc()
    {
        const T = document.getElementById("counter2").innerHTML;
        document.getElementById("counter2").innerHTML = parseInt(T) + 1;
    }

    function ParentTest()
    {
        const T = document.getElementById("counter1").innerHTML;
        document.getElementById("counter1").innerHTML = parseInt(T) + 1;

        if (opener)
        {
            opener.FuncFromChildToParent();
        }
        
        setTimeout(ParentTest, 500);

    }
</script>

</body>
</html>

Czym to może być spowodowane, że dobrze to działa tylko na komputerze, a na telefonie już są kłopoty? Jak spowodować (i nie chodzi tu tylko o sam setTimeout), żeby skrypt JavaScript działał tak samo niezależnie od tego, czy jest w zakładce na wierzchu, czy pod spodem, skoro on ma działać?

2

https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#timeouts_in_inactive_tabs

Nie ma gwarancji, że timeout wykona się o czasie. Zawsze sprawdzaj, ile czasu faktycznie minęło. To jest dobre podejście nawet gdyby przeglądarki tego sztucznie nie ograniczało, bo timeout może się opóźnić z wielu innych powodów.

0

W tym przypadku nawet nie chodzi o super dokładność, mogę dopuścić obsuwę o 100ms, to nie problem, bo to nie będzie widoczne "gołym okiem". Problem w tym, że dzieje się tylko na telefonie, nie mam innych zadań JS ani nawet innych apek, a wystarczy, że zamienię widoczne okna/karty i już działa inaczej.

1

Nie bardzo jest co z tym zrobić. Karta w tle może zostać nawet całkiem ubita, a nie tylko timery mieć opóźnione, wtedy w ogóle klapa, bo czy Twoja appka jest gotowa rozpoznać po odświeżeniu karty, że jest częścią aplikacji? Zarówno "dziecko" jak i "rodzic"?

Zresztą nawet na desktopie Chrome od niedawna sam wg "widzimisia" może usuwać z pamięci karty w tle (tzn bodajże może od dawna, bo już w 2022 widzę podobne problemy na stack overflow, ale od naprawdę kilku wersji wstecz ma być to bardziej agresywne, nie korzystam z Chroma więc nie zaobserwowałem jak bardzo, choć ja mam też 64GB RAM to pewnie może nie ubijać tak intensywnie). Do tej pory widać to było głównie na mobilkach właśnie. W Edge chyba coś podobnego wcześniej wprowadzili, no ale z Chromium to się rozlezie szybko na inne przeglądarki.

Na dekstopie w chrome:flags znalazłem to:
screenshot-20240712090638.png

Co ciekawe, u mnie się okazało, że to coś jest wyłączone (może przez te 64 GB RAMu?):
screenshot-20240712090743.png

Ale jak widać można dodać stronę do wyjątków.

Więc jak appka to jest coś np. tylko dla jakiejś firmy, może nawet komputery+Chrome zarządzane z jakiejś domeny - i pójdzie to zdalnie ludziom poustawiać. Ale jak nie - to zostaje prosić użytkowników o ogarnięcie flagi i potem ustawień ręcznie.

No i najważniejsze - na mobilnej wersji Chrome tej flagi nie ma w ogóle.

W miarę możliwości - przemyśl architekturę tej aplikacji. Niech wszystko chodzi w jednej karcie. Inaczej to moim zdaniem jest ślepa uliczka. OSy, przeglądarki - one uwalą wszystko, co zrobi developer, żeby sobie podbić statystyki (szybkość aktywnej karty, mało ramu, oszczędność CPU, więc baterii). Problem znany z natywnych appek na Androidzie od lat: https://dontkillmyapp.com

Edit: Przeczytałem jeszcze raz opis flagi i w sumie ona tylko odblokowuje dodatkowe opcje, które nadal nic nie mówią:
screenshot-20240712091347.png

Ale można to wyłączyć całkiem bez tej flagi. Przynajmniej na desktopie.

0

czy Twoja appka jest gotowa rozpoznać po odświeżeniu karty, że jest częścią aplikacji? Zarówno "dziecko" jak i "rodzic"?

Ten problem już rozwiązałem wcześniej, tylko, że wyrzuciłem kod, który to robi, żeby nie zaciemniać sprawy. W dużym skrócie, każdy "członek rodziny" ma unikatowy numer, którym jest tak naprawdę zapisana wartość funkcji Date.now() do zmiennej globalnej (aktualna godzina z dokładnością do milisekundy). Rodzic, przy wywoływaniu dziecka, uruchamia funkcję, w której podaje swój numer i uzyskuje numer dziecka, więc każda strona zna numer drugiej strony. Aby wykonać czynność, to nie tylko sprawdzam istnienie drugiej strony i to, czy druga strona zawiera daną funkcję, ale też, czy zgadza się numer. Jeżeli po którejkolwiek stronie naciśnie się F5, to już będzie mieć inny numer, czyli już nie spełnia warunków. Testowałem to tylko na komputerze, ale zadziałało zgodnie z oczekiwaniem.

W miarę możliwości - przemyśl architekturę tej aplikacji. Niech wszystko chodzi w jednej karcie. Inaczej to moim zdaniem jest ślepa uliczka.

Pierwotnie miałem aplikację desktopową z wieloma okienkami, w której takie rozwiązanie sprawdzało się bardzo dobrze, dlatego postanowiłem zrealizować podobne rozwiązanie w aplikacji przeglądarkowej, żeby działała w sposób zbliżony do desktopowej. Np. jest to kilka widoków tej samej grafiki i ma się pełną swobodę i wygodę w rozmieszczeniu widoków na ekranie komputera.

Alternatywnym rozwiązaniem, które brałem pod uwagę jest zastosowanie iframe, umieszczane po kilka sztuk na interfejsie aplikacji w każdej ramce odpalone dziecko. Tutaj swoboda w przemieszczaniu dzieci jest ograniczona, ale zaletą jest to, że cała apka, przez cały okres użytkowania jest widoczna na jednej karcie i na wierchu. Idealnie to byłoby zastosować frameset, ale jest to rzecz przestarzała, niezalecana i niewspierana.

Czy dobrze rozumiem, ze w przypadku Iframe, już nie ma ryzyka, że przeglądarka samoczynnie sobie zamrozi, ubije, odświeży któreś dziecko w ramce iframe?

Zresztą nawet na desktopie Chrome od niedawna sam wg "widzimisia" może usuwać z pamięci karty w tle (tzn bodajże może od dawna, bo już w 2022 widzę podobne problemy na stack overflow, ale od naprawdę kilku wersji wstecz ma być to bardziej agresywne, nie korzystam z Chroma więc nie zaobserwowałem jak bardzo, choć ja mam też 64GB RAM to pewnie może nie ubijać tak intensywnie).

Jakim cudem to działa poprawnie? Przecież może być strona wygenerowana metodą POST, która np. jest skutkiem złożenia zamówienia i wyświetla numer tego zamówienia. Każde odświeżanie takiej strony to złożenie kolejnego zamówienia na to samo i można się nieźle zdziwić, jak przyjdzie za to zapłacić. W desktopach jest ostrzeżenie, czy na pewno odświeżyć, jak się naciśnie F5.

0

Nigdy nie widziałem, żeby ktoś rozpraszał aplikację jedną na wiele zakładek przeglądarki.

Taki frameworki js dobrze sobie radzą, żeby wszystko na jednej stronie robić, manipulować nimi tak, żeby się wydawało, że przechodzi się do innej strony, a to tylko stany zmienia.
Ale iframe chyba by były takie jak chcesz, johny z tego co widziałem to każdy element na stronie miał iframe, pewnie by ci coś doradził :>

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.