j.j.: Javascript: Warum hat Safari Probleme mit const?

Hallo!

Nach über 20 Jahren schau ich hier mal wieder rein.

Fast ein Jahr lief ein kleiner Internetauftritt problemlos, bis ich auf die idee gekommen bin, in der Scriptdatei einige var durch const zu ersetzen. Getestet mit Chromium und Firefox, alles ok.

Es hat acht Wochen gedauert, bis aufgefallen ist, daß in Safari das komplette Script ignoriert wird (Grundfunktionen brauchen nur CSS). Ich hab keinen Mac und kein Iphone und prüfe Safari lediglich mit online-Scrennshots, was Zeitaufwendig ist und ich wegen der Banalität der Änderung wohl für überflüssig gehalten habe (jajahaha...).

Frage: Kennt jemand das Problem? Bei meinen Recherchen bin ich auf einige Ergebnisse gekommen, aber nicht schlauer geworden.

Das Schript ist hier: peter-hindelang.de/Bilder/script.js Wenn man von den ersten sechs var eine oder mehrere durch const ersetzt, steigt Safari aus.

Es ist mir nicht gelungen, das Problem durch Versuch und Irrtum einzugrenzen. Die Welt wird auch mit var nicht untergehen.

  1. Hallo

    Fast ein Jahr lief ein kleiner Internetauftritt problemlos, bis ich auf die idee gekommen bin, in der Scriptdatei einige var durch const zu ersetzen. Getestet mit Chromium und Firefox, alles ok.

    Es hat acht Wochen gedauert, bis aufgefallen ist, daß in Safari das komplette Script ignoriert wird (Grundfunktionen brauchen nur CSS).

    Um welche Version des Safari geht es denn? Laut Can I use? unterstützt Safari sowohl in OS-X/MacOS als auch in iOS das Schlüsselwort const in JS seit Version 11.

    Leider kann ich nichts testen, aber du hast ja dein gesamtes Skript in einen Try-Catch-Block eingeschlossen. Gibt das catch(e) irgendwas aus?

    Tschö, Auge

    --
    „Habe ich mir das nur eingebildet, oder kann der kleine Hund wirklich sprechen?“ fragte Schnapper. „Er behauptet, nicht dazu imstande zu sein“ erwiderte Victor. Schnapper zögerte (…) „Nun …“ sagte er schließlich, „ich schätze, er muss es am besten wissen.“ Terry Prattchett, Voll im Bilde
    1. Um welche Version des Safari geht es denn? Laut Can I use? unterstützt Safari sowohl in OS-X/MacOS als auch in iOS das Schlüsselwort const in JS seit Version 11.

      Es geht auf jeden Fall um Safari 14, 15, 16, wahrscheinlich auch 17 (habs vergessen).

      Leider kann ich nichts testen, aber du hast ja dein gesamtes Skript in einen Try-Catch-Block eingeschlossen. Gibt das catch(e) irgendwas aus?

      Der catch-Block ist gedacht als Notbremse wenn es ganz schief läuft und für IE, Presto-Opera (Opera Mini) u.s.w. Wenn Safari den catch-Block erreicht hätte, wäre das Layout im Eimer. Das war nicht der Fall, die Seite hat ja großenteils funktioniert, was Teil des Problems war.

      Danke! j.j.

  2. Hallo j.j.,

    steigt Safari aus.

    Definiere das genauer.

    Und bringe im catch e.toString() zur Anzeige.

    Das muss etwas sein, was mit const eigentlich nichts zu tun hat.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo j.j.,

      steigt Safari aus.

      Definiere das genauer.

      Und bringe im catch e.toString() zur Anzeige.

      Das muss etwas sein, was mit const eigentlich nichts zu tun hat.

      Rolf

      Ich zitiere mich mal selber:

      Ich hab keinen Mac und kein Iphone und prüfe Safari lediglich mit online-Scrennshots

      j.j.

      1. Hallo

        … bringe im catch e.toString() zur Anzeige.

        Das muss etwas sein, was mit const eigentlich nichts zu tun hat.

        Rolf

        Ich zitiere mich mal selber:

        Ich hab keinen Mac und kein Iphone und prüfe Safari lediglich mit online-Scrennshots

        Ja, aber …

        Wie in meinem anderen Posting schon gesagt, kann der Safari das (const) seit Ewigkeiten. Da muss etwas anderes im Spiel sein – wie Rolf schon anmerkte – und das sollte auch in anderen Browsern zu (einer) Fehlermeldung(en) führen.

        Tschö, Auge

        --
        „Habe ich mir das nur eingebildet, oder kann der kleine Hund wirklich sprechen?“ fragte Schnapper. „Er behauptet, nicht dazu imstande zu sein“ erwiderte Victor. Schnapper zögerte (…) „Nun …“ sagte er schließlich, „ich schätze, er muss es am besten wissen.“ Terry Prattchett, Voll im Bilde
        1. Wie in meinem anderen Posting schon gesagt, kann der Safari das (const) seit Ewigkeiten.

          Ja, deshalb diese Frage.

          Da muss etwas anderes im Spiel sein – wie Rolf schon anmerkte – und das sollte auch in anderen Browsern zu (einer) Fehlermeldung(en) führen.

          Es gibt keine Fehlermeldung in anderen Browsern. Diese und auch Safari erreichen den catch-Block nicht.

          Aber breaking news: Ich habe eine Testseite aufgesetzt mit einem Testscript und per Screenshot (Safari 15.4) scheint es zu funktionieren, warum auch immer. Ich hatte zwischenzeitlich kleinere (?) Änderungen gemacht.

          Also, kann bitte jemand mit Safari mal testen? Es soll z.B. ein weißes Feld mit Titel und Größe des Bildes sichtbar werden und die gelben <input>s bei mouseover reagieren. Die Bilder sollen beim draufklicken in die Mitte drehen.

          Testseite: peter-hindelang.de/Bilder/test3.html

          Testscript mit const: https://peter-hindelang.de/Bilder/safari.js

          Danke!

          j.j.

          P.S. Fällt mir grad wieder ein: Bei meinen Recherchen war irgendwas mit Safari const block-scoping-Problemen, wenn man optionale {Klammern} wegläßt in speziellen Situationen oder so ähnlich. Das hab ich nicht auf mich bezogen, außerdem war es recht alt.

          1. Testscript mit const: https://peter-hindelang.de/Bilder/safari.js

            sorry, richtig ist: peter-hindelang.de/Bilder/safari-test.js

            1. @@j.j.

              Testseite: peter-hindelang.de/Bilder/test3.html

              Testscript mit const: https://peter-hindelang.de/Bilder/safari.js

              sorry, richtig ist: peter-hindelang.de/Bilder/safari-test.js

              Nein. Richtig ist: https://peter-hindelang.de/Bilder/safari-test.js

              Testseite: https://peter-hindelang.de/Bilder/test3.html

              Wenn du willst, dass sich jemand dein Zeugs ansieht, verlinke es.

              Kwakoni Yiquan

              --
              Ad astra per aspera
              1. @@Gunnar Bittersmann

                Testseite: https://peter-hindelang.de/Bilder/test3.html

                Safari meldet in der Konsole:

                ReferenceError: Can't find variable: BL

                Kwakoni Yiquan

                --
                Ad astra per aspera
                1. Hallo Gunnar,

                  Der zweite Unterschied zwischen var und const ist das Hoisting, das bei const nicht stattfindet.

                  Von der Programmstruktur her scheint mir das hier unproblematisch.

                  Ich verstehe nicht, warum er keine Closure bildet und BL darin einschließt.

                  Rolf

                  --
                  sumpsi - posui - obstruxi
                  1. Hallo

                    Der zweite Unterschied zwischen var und const ist das Hoisting, da bei const nicht stattfindet.

                    Von der Programmstruktur her scheint mir das hier unproblematisch.

                    Ich verstehe nicht, warum er keine Closure bildet und BL darin einschließt.

                    Vielleicht, weil die Struktur des HTML-Quelltexts der von Gunnar verlinkten Beispielseite kaputt ist. Es fehlt der Head-Bereich des Dokuments (<head></head>) und <body</body>. Alle Angaben, die im Head-Bereich stehen (meta, title und so) als auch der Content des Dokuments stehen direkt unter html. Damit muss schon irgendwas mit document.body… scheitern.

                    [edit]: Der von Gunnar genannte Fehler in Zeile 47 (das Fehlen der Variable BL) ist wohl nur ein Folgefehler aus diesem Code.

                    const Knödel = document.body.querySelectorAll("input");
                    const Haupt =  document.body.querySelector("main");
                    const Bild =   Haupt.querySelectorAll("img");
                    const BL =     Bild.length;
                    

                    Wenn document.body nicht tut, bleibt der Rest weg.

                    Tschö, Auge

                    --
                    „Habe ich mir das nur eingebildet, oder kann der kleine Hund wirklich sprechen?“ fragte Schnapper. „Er behauptet, nicht dazu imstande zu sein“ erwiderte Victor. Schnapper zögerte (…) „Nun …“ sagte er schließlich, „ich schätze, er muss es am besten wissen.“ Terry Prattchett, Voll im Bilde
                    1. Vielleicht, weil die Struktur des HTML-Quelltexts der von Gunnar verlinkten Beispielseite kaputt ist. Es fehlt der Head-Bereich des Dokuments (<head></head>) und <body</body>. Alle Angaben, die im Head-Bereich stehen (meta, title und so) als auch der Content des Dokuments stehen direkt unter html. Damit muss schon irgendwas mit document.body… scheitern.

                      Tschö, Auge

                      Hallo!

                      Dann wäre Safari aber nicht standardkonform, <html>, <head>, <body> sind implizt. Auch Safari hat seit ewig den genormten HTML-Parser implementiert (und auch KHTML und alle andern Engines haben das seit Angeginn der Zeit so gehandhabt).

                      j.j.

                      1. Hallo

                        Vielleicht, weil die Struktur des HTML-Quelltexts der von Gunnar verlinkten Beispielseite kaputt ist. Es fehlt der Head-Bereich des Dokuments (<head></head>) und <body</body>. Alle Angaben, die im Head-Bereich stehen (meta, title und so) als auch der Content des Dokuments stehen direkt unter html. Damit muss schon irgendwas mit document.body… scheitern.

                        Dann wäre Safari aber nicht standardkonform, <html>, <head>, <body> sind implizt. Auch Safari hat seit ewig den genormten HTML-Parser implementiert (und auch KHTML und alle andern Engines haben das seit Angeginn der Zeit so gehandhabt).

                        An der Stelle lautet die Frage wohl eher, ob die JS-Engine das auch so sieht. Ich kann sie nicht beantworten, aber die von Gunnar per Screenshot gezeigte Fehlermeldung lässt mich daran zweifeln. Laut der Meldung in der Konsole ist die Variable BL schlicht nicht auffindbar.

                        So, wie ich das sehe, läuft da folgendes ab.

                        // kein <body> => Haupt = null
                        const Haupt =  document.body.querySelector("main");
                        // Haupt === null => Bild = null
                        const Bild =   Haupt.querySelectorAll("img");
                        // Bild === null => BL = null
                        const BL =     Bild.length;
                        

                        Ob ich das wirklich richtig sehe, kann ich so zwischen Tür und Angel sowie ohne Safari aber nicht beurteilen.

                        Tschö, Auge

                        --
                        „Habe ich mir das nur eingebildet, oder kann der kleine Hund wirklich sprechen?“ fragte Schnapper. „Er behauptet, nicht dazu imstande zu sein“ erwiderte Victor. Schnapper zögerte (…) „Nun …“ sagte er schließlich, „ich schätze, er muss es am besten wissen.“ Terry Prattchett, Voll im Bilde
                        1. Moin,

                          So, wie ich das sehe, läuft da folgendes ab.

                          // kein <body> => Haupt = null
                          const Haupt =  document.body.querySelector("main");
                          // Haupt === null => Bild = null
                          const Bild =   Haupt.querySelectorAll("img");
                          // Bild === null => BL = null
                          const BL =     Bild.length;
                          

                          Ob ich das wirklich richtig sehe, kann ich so zwischen Tür und Angel sowie ohne Safari aber nicht beurteilen.

                          müsste nicht schon beim Initialisieren von Bild eine Null-Pointer-Exception geworfen werden, weil Haupt null ist und dementsprechend null.querySelectorAll(…) nicht aufgerufen werden kann?

                          Viele Grüße
                          Robert

                          1. Hallo

                            So, wie ich das sehe, läuft da folgendes ab.

                            // kein <body> => Haupt = null
                            const Haupt =  document.body.querySelector("main");
                            // Haupt === null => Bild = null
                            const Bild =   Haupt.querySelectorAll("img");
                            // Bild === null => BL = null
                            const BL =     Bild.length;
                            

                            Ob ich das wirklich richtig sehe, kann ich so zwischen Tür und Angel sowie ohne Safari aber nicht beurteilen.

                            müsste nicht schon beim Initialisieren von Bild eine Null-Pointer-Exception geworfen werden, weil Haupt null ist und dementsprechend null.querySelectorAll(…) nicht aufgerufen werden kann?

                            Ich stochere hier nur, da ich den Fehler im Firefox unter Windows nicht reproduzieren kann. Der von Gunnar gezeigte Fehler tritt nicht schon bei der Initialisierung und „Befüllung“ der Variable auf, sondern erst bei der Verwendung von BL (zu dem zeitpunkt in Zeile 47).

                            Heute vormittag, als ich mir den Code angeschaut habe, stand dort noch …

                            for(var Arr = [], i = 0; i < BL; i++)
                                Arr.push(+ Bild[i].getAttribute("width"));
                            

                            … später hat j.j. den Code durch …

                            for(var Arr = [], i = 0; i < Bild.length; i++)
                                Arr.push(+ Bild[i].getAttribute("width"));
                            

                            … ersetzt, was offensichtlich auch nichts gebracht hat. Aber das sind, wie schon gesagt, meinerseits nur Mutmaßungen.

                            Tschö, Auge

                            --
                            „Habe ich mir das nur eingebildet, oder kann der kleine Hund wirklich sprechen?“ fragte Schnapper. „Er behauptet, nicht dazu imstande zu sein“ erwiderte Victor. Schnapper zögerte (…) „Nun …“ sagte er schließlich, „ich schätze, er muss es am besten wissen.“ Terry Prattchett, Voll im Bilde
                        2. Hallo Auge!

                          An der Stelle lautet die Frage wohl eher, ob die JS-Engine das auch so sieht.

                          Der HTML-Parser ist meineswissens tatsächlich in allen Browserengines exakt der aus der HTML-Norm. Er konstruiert das DOM (das ist der Elementbaum, den Du in den Entwicklerwerkzeugen der Browser siehst). Der Browser (also auch die JS-Engine) verwendet das DOM und nicht den Quelltext.

                          Ein paar Links:

                          (Neu war für mich übrigens die Info in Selfhtml, daß der Google HTML/CSS Style Guide das weglassen empfiehlt)

                          j.j.

                2. @@Gunnar Bittersmann

                  Testseite: https://peter-hindelang.de/Bilder/test3.html

                  Safari meldet in der Konsole:

                  ReferenceError: Can't find variable: BL

                  Danke! Ich hab BL in Zeile 49 mal ersetzt mit Bild.length. Der Fehler dürfte einfach weitergewandert sein zum nächsten Problem.

                  Welche Version hast Du?

                  j.j.

                  1. @@j.j.

                    Welche Version hast Du?

                    17.2.1.

                    Ich hab’s auf dem Mac getestet, nicht auf gekoppeltem iPhone.

                    Kwakoni Yiquan

                    --
                    Ad astra per aspera
          2. Aber breaking news: Ich habe eine Testseite aufgesetzt mit einem Testscript und per Screenshot (Safari 15.4) scheint es zu funktionieren, warum auch immer. Ich hatte zwischenzeitlich kleinere (?) Änderungen gemacht.

            Pahhh... zu früh gefreut! Funktioniert NICHT, und jetzt zeigt der Screenschor, daß Safari den catch-Block erreicht hat (Layout geändert, "Ihr alter Browser tut’s hier nicht mehr richtig!"). Das war bei meinen letzten Tests vor drei Wochen anders.

            Also, kann bitte jemand mit Safari mal testen? Es soll z.B. ein weißes Feld mit Titel und Größe des Bildes sichtbar werden und die gelben <input>s bei mouseover reagieren. Die Bilder sollen beim draufklicken in die Mitte drehen.

            Testseite: peter-hindelang.de/Bilder/test3.html

            Testscript mit const: https://peter-hindelang.de/Bilder/safari.js

            Testscript richtig: peter-hindelang.de/Bilder/safari-test.js

            Danke!

            j.j.

  3. Hallo j.j.,

    nochmal von vorn.

    Du bindest das Script mit defer ein - okay. Damit gibt's keine Probleme mit einer Race Condition.

    Dein gesamter Code hat diesen Grundaufbau:

    try
    {
       const xy;
    
       function trans() 
       {
          // use xy -> Reference Error
       }
    
       (onresize=function() {
          ...
          trans()
       })();
    }
    catch (e) {
    }
    

    Dieser Code enthält einige Gemeinheiten, und bei Gunnar gab's ja auch schon im Desktop-Safari einen Error.

    Die Zuweisung an onresize registriert einen Eventhandler für das Resize-Event auf dem window-Objekt. Im gleichen Zuge rufst Du ihn auch gleich das erste Mal auf. Danach wird er bei jedem Resize des Fensters erneut aufgerufen.

    Der große Unterschied zwischen const und var ist hier: var wird im globalen Scope definiert (weil var keine Blockscopes beachtet). const wird hingegegen im Blockscope des try/catch definiert. Sobald der try-Block durch ist, endet der Scope, in dem xy definiert wird und xy ist ein Kandidat für den Garbage Collector.

    Der es aber leben lassen sollte, denn:

    • function trans referenziert xy. Solange es trans gibt, gibt es xy (trans bildet eine Closure und sollte den try-Scope einschließen)
    • Der resize-Handler referenziert trans. Solange es den resize-Handler gibt, gibt es trans (der resize-Handler bildet eine closure, die trans und auch xy einschließt)
    • der resize-Handler wurde an window.onresize zugewiesen. Damit sollte er bis zu einer anderen Zuweisung oder bis zum Reload der Seite existieren.

    Es ist MÖGLICH, dass diese Referenzbeziehungen, die xy (also dein BL) am Leben erhalten sollten, von Safari nicht korrekt beachtet werden. Ich habe das gerade in Chrome/Android betrachtet, da wird es korrekt erhalten. Safari kann ich nicht testen. Kann es sein, dass die JavaScript-Engine von Safari Closures nur auf Funktionsscope-Ebene bildet?

    Zum Error-Handling: Wenn der Browser (Safari oder sonst einer) den resize-Handler aufruft, dann ist der globale try/catch-Block nicht mehr gültig. Die Handler-Funktion wurde zwar darin definiert, aber der Exception-Kontext gilt nur beim ersten Script-Durchlauf. Nicht mehr, wenn später das resize-Event behandelt wird. Um Fehler im Resize-Handler mit try/catch zu behandeln, musst Du in dieser Funktion einen eigenen try/catch aufbauen.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf!

      Du bindest das Script mit defer ein - okay. Damit gibt's keine Probleme mit einer Race Condition.

      Wenn Du das auf den Kommentar in Zeile 73 beziehst (der nichts mit der const-Problematik zu tun hat): Die Bildhöhe wird mit CSS an die Bildschirmgröße angepaßt. in Zeile 73 weiß es (nur) Safari offenbar noch nicht, daß das Bild per CSS evtl. kleiner wurde und die Berechnung Bild[0].height / 500 ergibt immer 1. Also wird das später nochmal wiederholt.

      • function trans referenziert xy. Solange es trans gibt, gibt es xy (trans bildet eine Closure und sollte den try-Scope einschließen)
      • Der resize-Handler referenziert trans. Solange es den resize-Handler gibt, gibt es trans (der resize-Handler bildet eine closure, die trans und auch xy einschließt)
      • der resize-Handler wurde an window.onresize zugewiesen. Damit sollte er bis zu einer anderen Zuweisung oder bis zum Reload der Seite existieren.

      Testseite ohne onresize, keine Änderung: peter-hindelang.de/Bilder/test30.html

      Es ist MÖGLICH, dass diese Referenzbeziehungen, die xy (also dein BL) am Leben erhalten sollten, von Safari nicht korrekt beachtet werden. Ich habe das gerade in Chrome/Android betrachtet, da wird es korrekt erhalten. Safari kann ich nicht testen. Kann es sein, dass die JavaScript-Engine von Safari Closures nur auf Funktionsscope-Ebene bildet?

      Ich hab eine Testseite gemacht mit onresize, ohne try/catch, scheint es zu funktionieren:

      peter-hindelang.de/Bilder/test31.html Müßte man ausführlich testen.

      Was würde daraus folgen? Ist es allgemein so, wie Du schreibst:

      Kann es sein, dass die JavaScript-Engine von Safari Closures nur auf Funktionsscope-Ebene bildet?

      Wäre das nicht ein kapitaler Browserfehler, der allgemein bekannt sein sollte? Bei bugs.webkit.org hatte ich auch mal gesucht, aber wohl kaum die richtigen Begriffe verwendet. (Letztenendes werd ich sowieso bei var bleiben müssen, insbesondere weil ich den problematischen Browser nicht selber testen kann)

      j.j.

      1. Hallo j.j.,

        die Race Condition bezog sich auf nichts konkretes. Ist halt nur so dass man beim Einbinden von Scripten Timingfehler machen kann. Aber du hast sie vermieden, meine ich.

        Defer-Scripte laufen vor dem DOMContentLoaded Event, aber vor der ersten Layout-Phase. Anpassungen von Elementen an den Viewport kommen deshalb danach.

        Im übrigen habe ich ja auch keinen Safari.

        Wie machst du deine Screenshot-Tests?

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo an alle!

          peter-hindelang.de/Bilder/test31.html (Test ohne try/catch) scheint zu funktionieren. Vielleicht kann Gunnar Bittersmann mit seinem Mac nochmal testen.

          Für mich ist die Sache vorerst erledigt. Ich hab jetzt und vor drei Wochen genug Zeit verbracht und auch noch euch behelligt.

          Zusammenfassung:

          • Safari hat offenbar ein Problem oder eine Eigenheit die dazu führt, daß mein script.js nur funktioniert wenn ich var statt const verwende oder try/catch weglasse
          • Die Lösung ist wohl Ersteres, weil es so bisher funktioniert hat
          • Gesten hab ich nomal nachgedacht. Ich glaube gelesen zu haben, daß WebKit relativ kürzlich (letztes Jahr?) so einen Fehler behoben hat: Kann es sein, dass die JavaScript-Engine von Safari Closures nur auf Funktionsscope-Ebene bildet? Vielleicht hat man den try/catch-Fall dabei vergessen?
          • I decided not to care!
          • Ansonsten ist vorerst genug Zeit aufgewendet worden. Danke an alle!

          j.j.

        2. Wie machst du deine Screenshot-Tests?

          Rolf

          Nebenbei: peter-hindelang.de ist ein Low-Budget-Projekt. Da ist ein 30$-Plan bei den einschlägigen Diensten nicht drin.

          Ich glaube bei browserstack.com kann man mit einen Gratiskonto 3-Sekunden-Tests machen, aber das verwende ich nicht.

          Die langjährige Erfahrung zeigt, daß kostenlose Dienste für Safari-Screenshots schnell dicht sind (überlastet/geschlossen/kostenpflichtig), sobald sie sich rumgesprochen haben. Ich verwende derzeit zwei und sende Dir morgen eine private Nachricht. Jetzt muß ich ins wirkliche Leben.

          j.j.

        3. Wie machst du deine Screenshot-Tests?

          Rolf

          Seh grad, kann keine E-Mail senden weil keine Adresse angegeben. Z.B. webpagetest loc NY