Wikipedia:Technik/Skin/JS

Aus Wikipedia

Wikipedia:Technik/Skin/JS/LinkboxJavaScript im Wiki-Projekt


Auf dieser Seite wird dargestellt, wie sich JavaScript (JS) durch Benutzer zur Programmierung in einem Wiki-Projekt einsetzen lässt.

JavaScript lernen[Am Gwëntext werkeln]

Wer bereits irgendeine Programmiersprache kennt, wird mit JavaScript keine größeren Schwierigkeiten haben.

Einige Anleitungen:

Wikibooks: JavaScript – eppas zum Lerna und Lehra.

Einbindung[Am Gwëntext werkeln]

Angemeldete Benutzer können Benutzer-Unterseiten mit JavaScript-Code anlegen.[1]

Schnell und einfach durch Anklicken von: common.js – „Gemeinsames JS aller Benutzeroberflächen“. In diese Seite die gewünschten Programmzeilen einfügen.

Wer mit mehreren Skins jongliert, kann zusätzlich spezifische Definitionen anlegen, etwa vector.js. Die aktive Skin nebst einem Link zum spezifischen JS findest du unter:

Dies wird dann anschließend an common.js aktiviert und überschreibt gleichartige Definitionen. Skin-spezifisches JS allein geht auch.

Es gibt zusätzlich die Möglichkeit Konfigurationen wikiübergreifend (global) „Gemeinsames CSS/JavaScript für alle Wikis (weitere Informationen):“ festzulegen.

Testen vor dem Speichern[Am Gwëntext werkeln]

Bei einer Bearbeitung wird jedes Skript bereits ausgeführt, wenn man „Vorschau“ oder „Änderungen“ anklickt. Damit lässt sich die Funktion testen; zumindest die richtige Syntax kann überprüft werden. Wenn die Wirkung nicht auf bestimmte Seiten beschränkt ist, kann das Ergebnis getestet werden.

Wenn eine .js-Seite editiert wird, werden die (anderen) Standard-Skripte des Benutzers je nach aktueller Projektkonfiguration möglicherweise nur einmalig beim Öffnen zur Bearbeitung (edit) eingebunden; beim weiteren Bearbeiten (submit) nicht erneut.[2]

CodeEditor[Am Gwëntext werkeln]

Der CodeEditor bietet einen Editor für JS und CSS im Bearbeiten-Textfeld; etwa vergleichbar mit wikEd:

  • Syntaxhighlight
  • Anzeige der Zeilennummern

Seit 2013 wird er automatisch beim Bearbeiten der Benutzerseiten zugeschaltet, wenn der Seitenname auf .js (bzw. .css) endet. Seit 2014 wird auch automatisch eine Syntaxanalyse vorgenommen.

Test-Projekte[Am Gwëntext werkeln]

Im deutschsprachigen de.wikipedia.beta lassen sich Test-Szenarien aufbauen und sich gefahrlos und ohne andere Benutzer zu stören Seiten der deutschsprachigen Wikipedia originalgetreu simulieren. Damit kann realitätsnah, ohne echte Seiten zu beschädigen oder Versionsgeschichten aufzublähen, auch die Manipulation von Seiten oder das Verhalten bei fehlerhaftem Wikitext und Seiteninhalt erprobt werden.

Siehe dazu ausführlich WP:BETA.

Werkzeuge (Browser etc.)[Am Gwëntext werkeln]

Dieser Abschnitt ist mittlerweile ausgegliedert als: Technik/Browser/Entwicklerwerkzeuge.

Fehlermeldungen[Am Gwëntext werkeln]

Siehe hier.

Fehler finden (Debugging)[Am Gwëntext werkeln]

Siehe hier.

Syntax-Analyse[Am Gwëntext werkeln]

Siehe hier.

JavaScript eigenständig ausführen[Am Gwëntext werkeln]

Siehe hier.

Wiki-Seiten mit Quellcode[Am Gwëntext werkeln]

<nowiki> in Skripten[Am Gwëntext werkeln]

Auch wenn der Inhalt dieser Seiten wie Quellcode hervorgehoben ist, wird die Wikisyntax dennoch zu Teilen beachtet. Dies hat zur Konsequenz, dass Wiki-Konstrukte im Inneren (etwa Links oder Vorlagenbeginn) erkannt werden und das Skript bei diesen Seiten also unter Links auf diese Seite aufgelistet oder gar in eine Kategorie einsortiert wird. In den meisten Fällen ist dies nicht erwünscht. Auch würde eine irgendwo enthaltene Zeichenkette ~~~~ zur Signatur expandiert. Aus diesem Grund empfiehlt es sich, in einer der ersten Zeilen ein auskommentiertes

 // <nowiki>

und in einer der letzten Zeilen

 // </nowiki>

zu schreiben.

Taucht irgendwo im eigenen Skript die Zeichenkette </nowiki> auf, würde dies den nowiki-Bereich sofort beenden; das lässt sich durch Konstrukte wie "</"+"nowiki>" verhindern.

Manchmal ist die Interpretation des JavaScript als Wikitext jedoch erwünscht: Wenn man Skripte anderer Benutzer einbindet, so sollte man in einem Kommentar immer einen Wikilink auf die eingebundene Seite angeben, damit das einbindende Skript unter „Links auf diese Seite“ aufgelistet wird. In diesem Fall müssen die Import-Anweisungen bzw. die Kommentare mit den Wikilinks natürlich außerhalb der nowiki-Bereiche liegen.

Weiterleitungsseiten[Am Gwëntext werkeln]

Die normale #WEITERLEITUNG [[...]] ist unwirksam und würde die Ausführung der Seite verhindern.

Seit Sommer 2015 wird beim Verschieben einer JS-Seite automatisch eine JS-Weiterleitung mittels mw.loader.load() auf der bisherigen Seite generiert. Die bisherigen Verlinkungen sollten allerdings baldmöglichst aufgelöst werden, da es sonst zu merklichen Verzögerungen („Geruckel“) beim Lesen der Seiten kommen kann. Anders als bei Weiterleitungen von Wiki-Seiten, die bereits innerhalb des Servers aufgelöst werden, kommt es zu einer kompletten Runde von Netzwerk-Anfrage und Interpretation, bis die aktuellen Definitionen geladen werden können.

JS-Unterseiten[Am Gwëntext werkeln]

  • Die Seite erhält das content model javascript statt des sonst üblichen wikitext. Das hat unter anderem folgende Wirkungen:
    • Vorlagen wie etwa {{SLA}} werden nicht expandiert dargestellt, bleiben jedoch außerhalb von nowiki-Bereichen wirksam.
    • Bei der Seitenverschiebung wird keine Weiterleitungsseite mit #REDIRECT usw. angelegt. Allerdings wird seit Sommer 2015 automatisch ein Aufruf der neuen URL unter dem alten Seitennamen generiert; das verzögert die Antwort beim Benutzer durch eine zusätzliche Runde jedoch spürbar, und Einbindungen sollten dann baldmöglichst auf den neuen Seitennamen umgestellt werden.
    • Der CodeEditor wird aktiviert.
    • Das Vorstehende gilt seit 2018 auch für das content model JSON.
  • Bis zu einer gewissen Maximalgröße der Seite werden die Syntaxelemente in unterschiedlichen Farben dargestellt.
  • Seit Anfang 2021 werden auf Code-Seiten auch Zeilennummern angezeigt.
    • Eine Verlinkung auf eine bestimmte Zeile ist möglich mit dem Fragmentbezeichner #L-1, #L-2, #L-3 usw.
    • Ein Klick auf die Zeilennummer hebt die aktuelle Zeile hervor, und in der Adresszeile des Browsers wird die geeignete Verlinkung auf diese Zeile dargestellt.
    • In aktuellen Diskussionen kann auf die momentane Version verlinkt werden.
    • Weil sich der vorangehende Code im Lauf der Zeit ändern kann, wäre ggf. ein PermaLink ratsam.
    • Die Maximalgröße von Seiten mit Syntaxhervorhebung, die eine farbige Auszeichnung bei zu großen Seiten unterbindet, verhindert dann auch die Generierung der Zeilennummern.

Benutzerseiten[Am Gwëntext werkeln]

Für die JS-Unterseiten im Benutzernamensraum gilt:

  • Sie sollten immer auf kleingeschriebenes .js enden, damit sie von jeder beteiligten Software sicher erkannt werden (genauso: .css).
  • Die festgelegten Skin-spezifischen Definitionen müssen exakt so (klein) geschrieben werden wie die Skin technisch heißt. Am sichersten ist es, man generiert sie über die Einstellungen (siehe Einbindung).
  • Ansonsten kann Groß- und Kleinschreibung der Unterseite frei gewählt werden.
  • Außer dir können nur Administratoren deine JS-Unterseite verändern, was diese jedoch nicht tun werden.
  • Du kannst deine JS-Unterseite wie jede andere deiner Unterseiten löschen lassen, indem du am Anfang {{Löschen}} einfügst (möglichst JS-auskommentiert). Auch wenn die Vorlage nicht wie üblich dargestellt wird, funktioniert sie dennoch, sodass ein Administrator deinem Wunsch nachkommen und die Seite löschen wird.

Quellcode darstellen[Am Gwëntext werkeln]

Mit <syntaxhighlight> kannst du deinen Quelltext darstellen und dabei Syntaxelemente unterscheiden, wie es auch auf dieser Seite gehandhabt wird:

 <syntaxhighlight lang="javascript">
 function meineFunktion() {
     // ...
 }
 </syntaxhighlight>

Bis Mitte 2011 hieß das Schlüsselwort vorrangig source – dies wird weiterhin von der Software verstanden, sollte aber gelegentlich aktualisiert werden, weil inzwischen den menschlichen Lesern weniger vertraut.

Blockade durch Browser-Werkzeuge[Am Gwëntext werkeln]

Siehe Browser/Ressourceneinbindung.

Häufige Aufgaben[Am Gwëntext werkeln]

Laden anderer Skripte[Am Gwëntext werkeln]

Um andere Skripte einzubinden, gibt es verschiedene Möglichkeiten, je nachdem, wo sich das einzubindende Skript befindet:

Laden über den Titel einer Seite[Am Gwëntext werkeln]

Der häufigste Fall besteht darin, dass man ein Skript von einer Unterseite eines bestimmten Benutzers einbinden möchte, etwa Benutzer:Name/skript.js. Leider gibt es für diesen Fall keine Funktion, die in jeder Hinsicht zufriedenstellend ist.

In der Praxis wird man hierfür die Funktion importScript verwenden, der man als Parameter den Seitennamen übergibt:

importScript('Benutzer:Name/skript.js'); //[[Benutzer:Name/skript.js]]

Der Wikilink im JavaScript-Kommentar dient dazu, dass deine Seite über die Funktion „Links auf diese Seite“ gefunden werden kann, Schau aa oben.

Die Funktion importScript gilt allerdings als „deprecated“ und sollte folglich in neueren Skripten nicht mehr verwendet werden. Leider gibt es (noch) keine glückliche Alternative.

Module auf dem Server[Am Gwëntext werkeln]

Viele häufig verwendete Skripte, darunter besonders jQuery-Plugins, können direkt vom Server geladen werden. Dazu dienen die Funktionen mw.loader.load() und mw.loader.using(). Eine Liste vorhandener Module befindet sich hier; vollständig unter mw:ResourceLoader/Default modules.

Will man beispielsweise das jQuery-Plugin async laden, so kann man dies mit dem Befehl mw.loader.load('jquery.async'); tun. Man kann auch mehrere Dateien gleichzeitig laden:

mw.loader.load(['jquery.async', 'jquery.tipsy']);

lädt die beiden angegebenen Module. Um bestimmten Code auszuführen, nachdem das Modul angekommen ist, verwendet man mw.loader.using:

mw.loader.using('jquery.async',
                function () {
                   //dieser Code wird ausgeführt, wenn das Modul angekommen ist
                            },
                function () {
                  //dieser Code wird ausgeführt, wenn das Laden fehlschlägt
                            });

Die Funktion für den Fehlerfall kann auch weggelassen werden.

Laden über URL[Am Gwëntext werkeln]

Um eine JavaScript-Datei von einer bestimmten URL zu laden, wird die Funktion mw.loader.load verwendet.

So lädt

mw.loader.load("//en.wikipedia.org/w/index.php?title=User:PerfektesChaos/js/Utilities/r.js&action=raw&ctype=text/javascript");

das Skript en:User:PerfektesChaos/js/Utilities/r.js aus der englischsprachigen Wikipedia. Bei Wiki-Projekten setzt sich die URL dabei zusammen aus den Teilen:

  • title= – Titel der Ressource, in URL-kodierter Form
  • action=raw – zwingend erforderlich, weil sonst die HTML-Seite ausgeliefert wird
  • ctype=text/javascript – zwingend erforderlich zusätzlich zum Parameter MIME-Typ (in diesem Fall der für JavaScript, bei CSS-Dateien wäre ctype=text/css anzugeben), weil der Server sonst den Typ text/x-wiki im Feld Content-Type des HTTP-Header ausliefert.
  • Angaben zur Mindesthaltbarkeit im Browser-Cache

Es ist wahlweise anzugeben:

  1. eine vollständige (absolute) URL; diese muss mit http beginnen
  2. eine „Protokoll-relative URL“; diese beginnt mit // und „erbt“ das Protokoll (http oder https) von der momentanen Umgebung. Mittlerweile arbeiten die Wikis aber nur noch mit https, so dass gerade die sicherheitsanfälligen Skripte auch immer mit https vereinbart werden können. Für bestimmte Sicherheitsmechanismen der Browser ist es wichtig, dass hier keine Zugriffe mit einfachem http mehr erfolgen.

Die Funktion hat einen zweiten Parameter MIME-Typ. Standardwert ist text/javascript und kann wegfallen; bei CSS-Dateien muss aber text/css angegeben werden.

Laden mit Callback-Funktion[Am Gwëntext werkeln]

In manchen Fällen will man eine JavaScript-Datei von einer bestimmten URL laden und eine bestimmte Funktion ausführen, nachdem die Datei angekommen ist. In diesem Fall kann man jQuery.getScript(url, callback); verwenden. Man sollte allerdings beachten, dass solche Skripte nicht aus dem Browser-Cache geladen werden können, was einerseits den Vorteil hat, dass immer die aktuelle Version verwendet wird, andererseits aber auch zu spürbaren Verzögerungen führen kann.

Laden von registrierten Gadgets[Am Gwëntext werkeln]

Eine sichere Methode zum Laden der aktuellen Version bietet der ResourceLoader – vorausgesetzt, das Helferlein ist bereits für den Einsatz mit dem ResourceLoader registriert.

Veraltete Funktionen[Am Gwëntext werkeln]

Neben den hier vorgestellten Funktionen gibt es noch einige weitere, die aber keinerlei Vorteile bieten, vor allem importScriptURI und includePage. Sie sollten im Rahmen ohnehin notwendiger Überarbeitungen bald entfernt werden.

Laden von CSS[Am Gwëntext werkeln]

Um CSS-Dateien einzubinden gibt es ähnliche Möglichkeiten wie für JavaScript:

Über die Funktion mw.util.addCSS() (veraltet: appendCSS() aus wikibits) kann man auch direkt CSS-Code in die Seite einfügen.

Siehe dazu ausführlich: CSS – Erweiterte Möglichkeiten.

Asynchrone und simultane Ausführung[Am Gwëntext werkeln]

In den ersten Jahren, bis etwa 2010, hatten Browser der Reihe nach immer nur ein Skript ausgeführt, und danach mit dem nächsten begonnen. Diese Verkettung führte zu extremen Wartezeiten, bis die Seite aufgebaut war, weil heutzutage nicht zwei oder drei Skripte abgearbeitet werden, sondern teilweise Hunderte von Komponenten.

Heutzutage versuchen die Browser, möglichst viele Skripte gleichzeitig zu starten. Muss ein Skript auf etwas warten, etwa Aufbau der Seitenstruktur (DOM) oder das Ergebnis einer API-Abfrage, dann wird die Arbeitsleistung zu einer anderen Aufgabe verlagert und nach Eintreten des ausstehenden Ereignisses auch die ursprüngliche Aktion fortgesetzt.

Für die Programmierung muss man sich heutzutage genau überlegen, von der Erfüllung welcher anderen Voraussetzungen das eigene Skript abhängig ist, ggf. auch die Bereitstellung anstoßen, und danach abwarten, bis das erfüllt worden ist.

„Synchron“ meint, dass viele Prozesse gleichzeitig abgearbeitet werden; egal, ob auf unterschiedlichen Hardware-Bauteilen oder ob die kleinen Wartezeiten zwischen Arbeitsschritten softwareseitig von anderen Prozessen ausgenutzt werden, um die Hardware optimal auszulasten. „Asynchron“ heißt das historische Verhalten, bei dem ein Skript nach dem anderen in vorhersagbarer Reihenfolge ausgeführt wird.

„Synchronisation“ heißt ein Treffpunkt, bei dem Skripte aufeinander warten, bis alle Voraussetzungen geschaffen sind, dass die Bearbeitung fortgesetzt werden kann. Die Funktionen mw.loader.using(), mw.hook() und $() dienen genau dieser Unterstützung.

Warten, bis die Seite geladen ist[Am Gwëntext werkeln]

Vor der Einführung von MediaWiki 1.17 wurde alles JavaScript am Anfang der Seite geladen, was dazu führte, dass viele Skripte erst einmal abwarten mussten, bis der Rest der Seite geladen war, denn um beispielsweise die Links auf einer Seite zu manipulieren müssen diese natürlich erst einmal vorhanden sein. Seit MediaWiki 1.17 wird JavaScript teilweise erst am Ende geladen, sodass dieses Problem nicht mehr überall auftritt. Dennoch ist es weiterhin eine gute Idee, die Ausführung von Code zu verzögern, bis die Seite geladen ist. Außerdem arbeiten moderne Browser mehrere Aufgaben parallel ab und können durchaus schon mehrere Skripte gleichzeitig ausführen, während der Aufbau des DOM noch nicht abgeschlossen worden ist.

jQuery.ready[Am Gwëntext werkeln]

Insbesondere bei der Anwendung von jQuery-Funktionen auf den Seiteninhalt sollte man immer deren document-ready-Funktion nutzen, um die Ausführung des Codes zu verzögern:

$(function() {
      //Code
});

Statt einer anonymen Funktion kann man natürlich auch eine benannte Funktion verwenden.

Bis 2016 war auch eine gleichwirkende Funktion .ready() gebräuchlich; diese Form wird inzwischen nicht mehr empfohlen und sollte nach und nach umgestellt werden:[3]

jQuery(document).ready(function() {
       //Code
});

mw.hook[Am Gwëntext werkeln]

Seit Sommer 2013 gibt es mit mw.hook eine elegante Möglichkeit, eine Referenz auf den Content zu übernehmen, die auch beim Live Preview funktioniert:

mw.hook("wikipage.content").add(function ($content) {
   $content.find() //Code
});

Es ist außerdem sichergestellt, dass mw.util bereits geladen wurde und mw.util.$content zumindest einen gültigen Startwert enthält.

Wenn es sich um editierbaren Wikitext handelt (also nicht um Spezialseiten oder bloße Seitenansicht), dann ist es ratsam, nicht mehr das statische mw.util.$content zu verwenden, sondern den bei Live Preview oder sonstiger dynamischer Seitenvorschau aktualisierten Bereich.

Veraltete Funktionen[Am Gwëntext werkeln]

Vor der Einführung von MediaWiki 1.17 (2011) war addOnloadHook() die Funktion, die am häufigsten für diesen Zweck verwendet wurde. Sie führte die ihr übergebene Funktion zu dem Zeitpunkt aus, zu dem der Browser den HTML-Quelltext bis zum Ende eingelesen hatte. Inzwischen kann diese Funktion zu Problemen führen, da sie nicht mehr nur das Laden des HTML-Quelltextes abwartet, sondern auch das Laden sämtlicher darin eingebundener Bilder, etc. Dies kann das JavaScript spürbar verlangsamen. In nahezu allen Fällen kann sie ohne Probleme durch jQuery.ready ersetzt oder sogar weggelassen werden.

Konfigurationsvariablen auswerten[Am Gwëntext werkeln]

Eine Reihe von Parametern gibt Informationen über die MediaWiki-Installation und das Wiki-Projekt (Database) wie auch die momentane Aktion und Einzelheiten über den Artikel und Benutzer-Einstellungen. Eine Übersicht der wichtigsten Parameter ist auf der Unterseite Variablen aufgelistet.

HTML manipulieren[Am Gwëntext werkeln]

Um durch JavaScript die Seite zu verändern, kann man die üblichen Methoden zur DOM-Manipulation verwenden. Alternativ stehen die Möglichkeiten von jQuery zur Verfügung. (Überblick; Schau aa Skin/GUI)

Für häufige Aufgaben gibt es auch spezielle Funktionen, etwa mw.util.addPortletLink(), um einen Link in die Navigationsleiste einzufügen.

Änderungen durch MediaWiki 1.17 – mw-Objekt[Am Gwëntext werkeln]

Mit Einführung von MediaWiki 1.17 im Februar 2011 sind Praktiken veraltet („deprecated“) in drei Bereichen:

Das heißt: Es wird übergangsweise noch längere Zeit funktionieren, sollte aber bei neuen Skripten nicht mehr benutzt und im Zug von Überarbeitungen nach und nach aktualisiert werden.

Das mw-Objekt, das viele Hilfsfunktionen verfügbar macht, ist hier beschrieben.

Debug-Modus[Am Gwëntext werkeln]

In MediaWiki 1.17 neu eingeführt. In eine dynamische URL (mit title=) wird &debug=true eingefügt (oder ?debug=true an eine statische URL anhängen). Das kann explizit für die Lade-Anweisung einer Ressource erfolgen. Offenbar funktioniert dies auch für eine ganze Seite – etwa einen Artikel – und scheint sich dann auf alle „automatisch“ eingebundenen Ressourcen auszuwirken, darunter auch common.js sowie vector.js, monobook.js etc.

Im Debug-Modus unterbleibt bei Skripten und CSS das Komprimieren, was besonders beim Debuggen fehlerhafter Skripte nützlich ist.

Ab Mitte 2011 erfolgt gleichzeitig mit dem Komprimieren von JavaScript ein rudimentärer Syntax-Check, der schwere Strukturfehler meldet und das betroffene Einzelskript nicht einbindet.

Bei benutzerdefiniert geladenen Ressourcen erfolgt keine Komprimierung; langfristig sollen diese einbezogen werden.

Theoretisch könnte man in seinem Benutzerskript auch angeben:

 mw.config.set("debug", true);
 debug = true;

Das hat allerdings keinerlei Folgen, weil es nicht mehr auf die bereits automatisch eingebundenen Ressourcen wirken kann.

Allerdings kann später der aktuelle Wert an die selbst importierten Ressourcen übergeben werden; um nicht immer die Versionsgeschichte belasten zu müssen, kann ein Cookie zum Umschalten in den Debug-Modus und zurück eingesetzt werden.[Beispiel]

Browser-Cache[Am Gwëntext werkeln]

Eine häufige Ursache für seltsames Verhalten von Skripten und CSS-Definitionen ist, dass nicht die neuesten Versionen eingebunden sind.

Seit MediaWiki 1.17 (Anfang 2011) wird an die URL der Standard-JS jedes Benutzers (common.js zusammen mit dem Skin-JS, etwa vector.js) ein Zeitstempel angefügt, der die letzte Veränderung angibt. Damit wird immer die gültige Definition eingebunden; Leeren des Browser-Cache ist deshalb nicht mehr erforderlich, siehe Hilfe:Cache.

Das gilt aber nicht für Einbindung anderer JS-Skripte. Hier hat jede Version die identische URL und im Browser-Cache können deshalb veraltete Versionen vorkommen; insbesondere unmittelbar nachdem man sein Nicht-Standard-Skript bearbeitet hat.

Dies gilt genau analog für CSS wie common.css usw.

Löschung von der Festplatte[Am Gwëntext werkeln]

Der auf der entsprechenden Bearbeitungs-Seite angezeigte Hinweis „Beachte: Nach dem Speichern musst du deinen Browser anweisen, die neue Version zu laden“ stimmt zwar; die weiter angegebenen Aufforderungen zum Reload sind jedoch nur begrenzt wirksam. Dieses greift nur bei Ressourcen, die unter genau diesen URL in die aktuelle Seite eingebunden worden sind. Beim angegebenen Reload würde vorrangig die folgende URL aktualisiert:

  • https://de.wikipedia.org/wiki/Benutzer:mir/MeinSkript.js

Tatsächlich in die später genutzten Seiten eingebunden wird jedoch meist mit einer URL wie

  • https://de.wikipedia.org/w/index.php?title=Benutzer:mir/MeinSkript.js&action=raw&ctype=text/javascript

Eher ist ein solches Reload deshalb sinnvoll auf einer Seite, in die das Skript auch eingebunden ist. Das muss nicht in jedem Namensraum und bei jeder Aktivität in gleicher Weise der Fall sein. Stehen bei der Einbindung die Parameter in einer anderen Reihenfolge, kommt einer hinzu oder unterscheidet sich die zulässige Groß- und Kleinschreibung, dann werden diese Varianten durch einfaches Reload nicht aktualisiert.

Sinnvoller und sicherer ist (zusätzlich):

  • Chrome: Werkzeug/Schraubenschlüssel → Optionen → links: Details → Feld [Internetdaten löschen] → Häkchen mindestens bei „Cache löschen“ → Internetdaten löschen
  • Firefox: [Extras] → [Einstellungen] → [Datenschutz] → [kürzlich angelegte Chronik] → Häkchen bei [Cache] (nur dort) → [Löschen]
    • Frühere Versionen:[4] [Extras] → [Neueste Chronik löschen] → Häkchen bei [Cache] (nur dort) → [Löschen]
  • Internet Explorer: [Extras] → [Entwicklertools] → [Cache] → [Browsercache für diese Domain löschen]   (siehe Debugging)
  • Opera: unter Extras → Internetspuren löschen → Individuelle Auswahl → kompletten Cache leeren

Lebensdauer[Am Gwëntext werkeln]

Für CSS- und JS-Ressourcen von Wiki-Servern lässt sich in der URL eine Zeitdauer festlegen, während der ungeprüft die Version aus dem Cache benutzt werden soll. Nach Ablauf dieser Periode fragt der Browser beim Server nach, ob diese Version noch aktuell ist, und erhält entweder eine frischere Version oder kann die „Mindesthaltbarkeit“ um den gleichen Zeitraum verlängern:

 mw.loader.load("//de.wikipedia.org/w/index.php?title=Benutzer:mir/MeinSkript.js&action=raw&bcache=1&maxage=86400&ctype=text/javascript");

Damit wird das „Verfallsdatum“ auf 86400 Sekunden (ein Tag) für den Browser-Cache gesetzt.

Standardmäßig wird sonst verwendet:

  • 2678400 Sekunden (31 Tage) für den Browser; zeitweilig aber auch 0 Sekunden.

Der ResourceLoader generiert bei jeder abgerufenen Seite neue (unterschiedliche) URL für die Ressourcen, wenn diese sich geändert haben, und ist deshalb nicht auf das Cache-Management angewiesen. Trotzdem sind hier in $wgResourceLoaderMaxage Zeiträume festgelegt.

Um einen Bug im Internet Explorer (IE6) zu umgehen, der den letzten Teil einer URL als „Datei-Endung“ interpretiert, kann man vorsorglich das funktionslose &* zum Schluss anhängen. Weil * keine Dateiendung sein kann, kommt es auch im Internet Explorer nicht zu Seltsamkeiten.

Sicherheitsaspekte[Am Gwëntext werkeln]

Verschlüsselte Verbindung[Am Gwëntext werkeln]

Alle URL, mit denen Skripte eingebunden werden, sollten nunmehr ausdrücklich https:// voranstellen. Ohnehin ist seit Juni 2015 keine andere Antwort mehr möglich. Sicherheitsmechanismen in Browsern könnten Ladeversuche über http:// unterbinden; alle durch JS generierten Ressourcen-Einbindungen und WMF-Verlinkungen müssen ebenfalls mittels https erfolgen. Die teilweise bisher eingesetzten protokoll-relativen URL führen auch zu keinem anderen Ergebnis mehr.

Stellt man ohnehin die Einbindungen um auf loader.load(), kann gleich als URL benutzt werden: https://de.wikipedia.org/…

Erfolgen asynchrone API-Abfragen („Ajax“), werden sie aus Sicherheitsgründen nicht ausgeführt, wenn die Domain (de.wikipedia.org) nicht übereinstimmt; aber auch, wenn das Schema (http und https) nicht identisch ist – insbesondere darf eine Seite unter https keine Ajax-Inhalte unter http akzeptieren.

Bösartige Wiki-Seiten und Skripte[Am Gwëntext werkeln]

Durch seine eingebundenen Skripte kann man unbemerkt fehlerhafte Aktionen im Namen seines WMF-Benutzerkontos ausführen. Insbesondere könnten böswillige Programmierer absichtlich Nebeneffekte in Skripten verstecken. Das wäre besonders interessant, wenn der momentane Benutzer über Administratorrechte oder mehr verfügt. Es muss noch nicht einmal vom Skript-Autor in JavaScript programmiert worden sein; es kann auch etwa auf einer besuchten Wiki-Seite ein Injection-Exploit verborgen sein. Neben wirksamen Aktivitäten, die irgendwann bemerkt werden und repariert werden können, ist auch das verdeckte Ausspähen möglich – von persönlichen und WMF-Informationen, die nicht öffentlich sichtbar sein sollen. Besonders einfach: Die E-Mail-Adresse oder schon die Zuordnung zwischen IP-Adresse und Wiki-Benutzer und Browser-Profil; auch ein Tracking sämtlicher besuchter Wiki-Seiten.

Normalerweise sind Klicks auf eine externe Website ungefährlich. Sowohl die Wiki-Software wie auch die gängigen Sicherheitseinstellungen der Browser verhindern Cross-Site Scripting (im weiteren Sinne) recht zuverlässig. Skripte beim angemeldeten Benutzer unterlaufen jedoch die bei der Darstellung von Wiki-Seiten erfolgte Abschirmung.

Verlinkung[Am Gwëntext werkeln]

Die größte Sicherheitslücke, die von gutwilligen Programmierern geöffnet werden kann, entsteht durch Verlinkung (Weblink). Erst ein aktiver Klick des Benutzers auf ein Link ermöglicht die Verbindung mit der Außenwelt, während sonstige Skript-Aktivitäten durch das Sicherheitskonzept der Browser begrenzt werden.

Allgemein ist es für die Funktionalität von Links etwa auf Wiki-Seiten sinnvoll, Sonderzeichen in der URL korrekt zu kodieren (URL-Encoding). Besonders wichtig ist dies aber unter Sicherheitsaspekten für die beiden Zeichenketten-Begrenzer ' und ", weil sie eine andere Interpretation des Links ermöglichen. Wenn man die auf einer Seite oder im Ergebnis einer API-Abfrage gefundenen Zeichenketten durch das Skript in Links (auch Bild-Einbindungen) einbaut, muss sorgfältig programmiert werden.

Die aktuellen JavaScript-Standardfunktionen leisten dies nicht:

  • encodeURI() (nur ")
  • encodeURIComponent() (nur ")

Mit unseren eigenen Funktionen werden dagegen auch ' unschädlich gemacht:

Beispiel: Ein Skript könnte etwa einen Link zu Benutzerseiten erzeugen mit dem Code

 var html = '<a href="/wiki/' + benutzer + '">Benutzerseite</a>'; //FALSCH!

Wäre der Benutzer, auf dessen Seite verlinkt wird, nun aber Benutzer:X" onclick="alert('XSS');" title="y, so würde der folgende HTML-Code erzeugt:

 <a href="/wiki/Benutzer:X" onclick="alert('XSS');" title="y">Benutzerseite</a>

Beim Anklicken würde der eingeschleuste JavaScript-Code ausgeführt.

Der Code zur Link-Erzeugung sollte daher besser lauten:

 var html = mw.html.element( 'a', {href: mw.util.getUrl( benutzer )}, 'Benutzerseite' );

eval-Funktion[Am Gwëntext werkeln]

Mit der eval-Funktion (JavaScript-Standardfunktion) lässt sich eine beliebige Zeichenkette als JavaScript-Code auswerten. Diese Funktion sollte nur im begründeten Ausnahmefall benutzt werden. Wenn überhaupt, muss die Zeichenkette (die aus einer Wiki-Seite stammen mag) vorher auf unerwartete JavaScript-Syntax analysiert werden: Die beiden Zeichenketten-Begrenzer '", die Klammern {} und das Semikolon ; dürfen nicht im importierten Text enthalten sein; möglichst auch nicht die Klammern ().

URL kodieren[Am Gwëntext werkeln]

Siehe dazu ausführlich unter Encoding.

Abhängigkeit mehrerer Skripte[Am Gwëntext werkeln]

Bis Sommer 2011 wurden Skripte, die nacheinander eingebunden wurden, auch eines nach dem anderen geladen. Angenommen, es wurde vereinbart:

 importScript("Benutzer:Mir/allerlei.js");
 importScript("Benutzer:Mir/braucht1.js");

Dann waren bei der Ausführung von braucht1.js die Definitionen in allerlei.js bekannt.

Mit Firefox 3.6.20 und später sowie Google Chrome werden Ressourcen aber nicht mehr nacheinander geladen, sondern das Laden wird praktisch gleichzeitig initiiert. So wird von modernen Browsern ein schnellerer Seitenaufbau bezweckt. Damit sind in braucht1.js die Funktionen von allerlei.js aber nicht verfügbar.

Nachfolgend werden Alternativlösungen vorgestellt. Dabei sind: Mir=Benutzername, allerlei.js=Skriptbibliothek mit Hilfsfunktionen, braucht1.js=Anwendungsskript, allerlei_init=irgendeine Funktion oder Variable, die in allerlei.js definiert wird.

Explizites Callback[Am Gwëntext werkeln]

In Benutzer:Mir/allerlei.js als letzte Zeilen:

 var i;
 if ( typeof window.allerlei_callbacks === "object" ) {
    for ( i = 0;  i < window.allerlei_callbacks.length;  i++ ) {
       window.allerlei_callbacks[i]();
    }
 }

und in Benutzer:Mir/braucht1.js zum Aufruf der Funktion braucht1_tuwas(), die Funktionen aus allerlei.js benötigt:

 if ( typeof allerlei_init === "undefined" ) {
    if ( typeof window.allerlei_callbacks === "undefined" ) {
       window.allerlei_callbacks = [ braucht1_tuwas ];
       mw.loader.load("https://de.wikipedia.org/w/index.php?title=User:Mir/allerlei.js&action=raw&ctype=text/javascript");
    } else {
         window.allerlei_callbacks.concat([ braucht1_tuwas ]);
    }
 } else {
    window.braucht1_tuwas();
 }

Das berücksichtigt auch, dass mehrere Skripte gleichzeitig allerlei.js benötigen könnten. Dies funktioniert auch in anderen Umgebungen; benötigt eine globale Variable allerlei_callbacks.

jQuery-Event[Am Gwëntext werkeln]

2011 sind auf Wiki-Seiten viele jQuery-Methoden vorhanden. Mit jQuery().trigger() und jQuery().bind() lässt sich dies alternativ formulieren; am Ende von allerlei.js:

 jQuery(document).trigger('loadWikiScript', ['Mir/allerlei']);

und in braucht1.js:

 if (typeof(allerlei_init) === "undefined") {
    jQuery(document).bind('loadWikiScript',
                          function (e, name) {
                             if (name === 'Mir/allerlei') {
                                braucht1_tuwas();
                             }
                          });
    mw.loader.load("https://de.wikipedia.org/w/index.php?title=User:Mir/allerlei.js&action=raw&ctype=text/javascript");
 } else {
    braucht1_tuwas();
 }

Dabei wird von allerlei.js ein Event vom selbst definierten Typ „loadWikiScript“ mit einem eindeutigen Identifizierer „Mir/allerlei“ ausgelöst. In braucht1.js wurde vereinbart, dass beim Auftreten dieses Ereignisses die unbenannte Funktion ausgeführt werden soll, in der bei passendem Identifizierer die zurückgestellte Funktion ausgeführt wird.

ResourceLoader[Am Gwëntext werkeln]

Für die Server-seitigen Skripte gibt es mit der Funktion mw.loader.using() des ResourceLoaders eine hervorragende Lösung, die genau auf diese Situation abstellt.

TimeOut[Am Gwëntext werkeln]

Aus mehreren Gründen ist nicht zu empfehlen, ein setTimeOut() dazu zu verwenden, alle Hundertstelsekunde nachzugucken, ob das gewünschte Skript denn inzwischen geladen wurde. Es bindet Rechnerleistung beim Betrachter; läuft unbegrenzt (solange keine andere Seite geladen wird) falls das gewünschte Skript nicht geladen werden kann; eröffnet möglicherweise neue Threads, während auf dem abgestorbenen Thread noch asynchrone Ereignisse eintreffen.

Aufruf über die Browser-Adresszeile[Am Gwëntext werkeln]

Genau wie http:// kann am Beginn einer URL das Pseudo-Schema javascript: in das Browser-Adressfeld geschrieben werden. Der anschließend eingetragene JavaScript-Code wird dann ausgeführt.[5][6][7] In HTML-Dokumenten hingegen kann es sein, dass aus Sicherheitsgründen der Browser die Ausführung einer Verlinkung auf javascript: verweigert; beim manuellen Kopieren in die Adresszeile und aus Bookmarks wird das aber wohl akzeptiert.

Dem auszuführenden Code sind sämtliche JavaScript-Funktionsdefinitionen und auch die Werte globaler Variablen bekannt, die seit dem Laden der Seite benutzt wurden. Das lässt sich für Bookmarklets ausnutzen, indem Funktionen und Hilfsfunktionen in den eigenen Skripten auf Vorrat definiert werden, so dass die Definition des Bookmarklets kurz und knapp ausfallen kann. Eine weitere Anwendungsmöglichkeit ist der Start von Diagnostik- und testweisen Funktionen, etwa um die Wirkung eines noch nicht vorhandenen Buttons auszuprobieren.

Beispiel:
javascript:insertNormdaten()
javascript:var a=3,b=4;alert(Math.sqrt(a*a+b*b))

Dieses einfache Format genügt in den meisten Fällen. Bei manchen Browserversionen und in manchen Konstellationen ist es erforderlich, ein zusätzliches Funktionsformat mit runden Klammern zu gestalten:
javascript:(var a=3,b=4;alert(Math.sqrt(a*a+b*b));)();
Andere Browser vertragen genau dies nicht.

Browser-Cookies lassen sich vom etwas fortgeschrittenen Benutzer einsetzen, um besondere Ziele zu erreichen:

  1. Es lassen sich Konfigurationen (auch vorübergehend) ändern, ohne jedesmal die Versionsgeschichte seiner common.js usw. zu belasten.
  2. Es lassen sich lokal im Browser Informationen abspeichern, die die Anonymität des Benutzers gefährden und nicht weltweit sichtbar sein sollen.

Mit Werkzeugen können nun die Inhalte der Cookies editiert werden, ohne die Benutzerseiten ändern zu müssen. Man kann sich aber auch eine Benutzer-Unterseite anlegen und in ihr ein kleines Formular anzeigen, das den momentanen Inhalt aller Konfigurationswerte zeigt sowie Schaltelemente zum komfortablen Ändern der Parameter.

  • Internet Explorer: [Extras] → [Entwicklertools] → [Cache] → [Cookies ansehen]   – und wie bearbeiten?
  • Firefox: Add-On, etwa Add N Edit Cookies+ und andere; im Firebug lassen sich die Werte ansehen und auch manuell ändern.

Die jQuery-Funktion jQuery.cookie() ist in Wiki-Projekten standardmäßig vorhanden und ermöglicht einfaches Lesen und Setzen der Werte.

Zu beachten ist, dass der Inhalt des Pfad-Parameters des Cookies immer „/“ sein sollte, damit er von beliebigen Wiki-Seiten aus gelesen werden kann.

Allzu vertraulich (Passwörter) sollten die Werte nicht sein. Ein böswilliges Skript eines anderen Benutzers könnte die Information ausspähen; es hätte aber ziemliche Schwierigkeiten, diese Daten unbemerkt und spurenlos an den neugierigen Benutzer zu übermitteln. Die Wiki-Server bekommen beim Betrachten jeder Seite die gesamte Wiki-Keksdose zu sehen.

Wie läuft ein Benutzer-Skript im Kontext ab?[Am Gwëntext werkeln]

Für die Modifikation von Elementen einer Wikipedia-Seite war bisher die Kenntnis des Ablaufs zwingend erforderlich gewesen, weil zu unterschiedlichen Zeitpunkten Skripte und Funktionen geladen und ausgeführt wurden und der Seiteninhalt nicht von vornherein definiert ist. Inzwischen erfolgt das Laden des Benutzerskriptes am Ende der HTML-Seite; damit ist deren Inhalt bereits bekannt. Einzelheiten änderten sich möglicherweise im Verlauf des Jahres 2011. 2012/13 ist die Weiterentwicklung irgendwie zum Erliegen gekommen; die nachstehende Abfolge gibt nur das Prinzip wieder, ist aber möglicherweise weder aktuell noch zukunftssicher.

Bei verschachtelten und verzwickten Aufrufen mehrerer Skripte, die voneinander abhängen, kann eine intensivere Beschäftigung mit den zeitlichen Abläufen erforderlich sein.

  1. Die Wiki-Seite wird vom Benutzer aufgerufen.
  2. Die HTML-Seite wird in den Browser geladen.
  3. Der Kopfteil <HEAD>..... der HTML-Seite wird eingelesen:
  4. Früher wurde jetzt das Skript wikibits.js neben anderen projektweiten Skripten geladen, und alle JavaScript-Variablen definiert. Weiterhin wurde bereits jetzt das benutzerdefinierte Skript wie etwa vector.js geladen und abgearbeitet. Dies geschieht ab 16. Februar 2011 zum Schluss.
  5. Seit Anfang April 2011 gibt es für die Entwickler im PHP-Bereich die Möglichkeit, bestimmte Skripte bereits im Kopfbereich zu laden und auszuführen. Dies ist wichtig, wenn das Layout der noch aufzubauenden Seite grundsätzlich beeinflusst werden soll. Allerdings vergrößert sich die Zeit etwas, bis für den Leser der Seitenaufbau beginnt. Möglicherweise wird eines Tages diese Möglichkeit auch für Benutzerskripte verfügbar gemacht; das setzt zunächst ein Haupt-Benutzerskript im Kopfbereich voraus.
  6. Mit importScript (bzw. importScriptURI) eingebundene Skripte werden im Februar 2011 an dieser Stelle eingebunden. Es kann sein, dass die Einbindung plötzlich nach den anderen Skripten am Ende des Dokuments erfolgt. Weil die Einbindung erst von den Skripten am Ende des Dokuments ausgelöst werden kann, erfolgt das Laden und die Ausführung erst nach ihnen.
  7. Allgemeine Wikimedia-Styles (CSS) werden geladen.
  8. Ein eventuelles common.css und anschließend die zur jeweiligen Skin gehörende vector.css oder monobook.css werden geladen.
  9. Spezifische CSS für bestimmte Hilfsmittel und Sonderfunktionen werden geladen.
  10. Dann wird der eigentliche HTML-Dokumenteninhalt geladen, genauer gesagt das </HEAD> wurde erreicht und der Abschnitt <BODY>… wird gelesen. Jetzt wird die Objektkomponente document mit Inhalt gefüllt; bis dahin hatte sie möglicherweise den Wert null, zumindest waren ihre Komponenten, die das DOM bilden, undefiniert oder leer.
  11. Nunmehr werden alle <div> und <form> gelesen und die entsprechenden Knoten im Objekt document definiert.
  12. Als letzte Elemente vor dem </BODY> werden (ab MediaWiki 1.17) die Skripte gelesen und gestartet:
    • Systemskripte der MediaWiki-Software
    • Gadgets (Helferlein)
    • Kombiniertes Benutzer-Skript, bestehend zunächst aus dem zur Skin gehörenden wie vector.js oder monobook.js und anschließend dem möglicherweise vorhandenen common.js
    • mw.user.options werden (in 1.17) jetzt mit Werten belegt.
    • Vom Benutzer-Skript der Skin durch Import-Aufrufe von mw.loader.load() vorgemerkte Skripte werden geladen und gestartet, wenn dies nicht über $() verzögert wurde.
    • Skripte können das ursprüngliche DOM verändern.
    • Auch nachträglich zugeladene CSS-Deklarationen können noch die Darstellung des Dokuments verändern. Sie werden der <HEAD>-Sektion angefügt.
  13. Das HTML-Element </BODY> wird erreicht. Das Ereignis DOMContentLoaded wird von neueren Browsern ausgelöst.
  14. jQuery erhält jetzt den Status document.ready. Mit $() (entsprechend jQuery(document).ready()) vorgemerkte Funktionen werden ausgeführt. Sie können weitere Skripte importieren sowie das ursprüngliche DOM verändern.
  15. Ressourcen des Dokuments (vor allem Bilder) werden geladen und dargestellt. Dies erfolgte möglicherweise bereits parallel mit den vorigen Prozessen. Das gesamte Dokument wird dargestellt.
  16. Erst nach vollständiger Darstellung aller Elemente wird von aktuellen Browsern das Ereignis window.onLoad ausgelöst. Frühere Browser hatten dies bereits mit dem Lesen des </BODY> vorgenommen. Auch ein neues DOM3.load Ereignis kann aufgetreten sein.
  17. Mit dem völlig veralteten addOnloadHook() zurückgestellte Funktionen aus den Benutzer-Skripten werden möglicherweise erst jetzt geladen. Sie können weitere Skripte importieren. Das Dokument kann dadurch verändert werden; es wird dann in veränderter Form dargestellt.
  18. Ergebnisse asynchroner Prozesse (etwa API-Abfragen) können noch später eintreffen und das Dokument (DOM) verändern; es wird dann in geänderter Form dargestellt.

Grundsätzlich ist es eine gute Idee, zunächst den Aufbau der für Anwender sichtbaren Seitenelemente abzuwarten und erst danach, wenn der Mensch eine halbwegs sortierte Seite präsentiert bekam, diskret im Hintergrund Kleinigkeiten zu verbessern, unsichtbare Details an unsichtbaren Untermenüs zu justieren oder auf API-Ergebnisse zu warten.

Im Zuge der Einführung von MW 1.18 im Herbst 2011 werden weitere Techniken angewendet.

  • Bei Modulen auf Server-Ebene kann bestimmt werden, ob sie frühzeitig (im HEAD) oder nach dem Seitenaufbau (LoadDocument) geladen werden sollen, um nachträglichen Layoutwechsel oder aber unnötige Wartezeit zu vermeiden.
  • Für Benutzer ist dies nicht unmittelbar vorhanden, kann jedoch über $() gesteuert werden.

Es ist zu erwarten, dass die Technologie auch in 2012 weiter entwickelt wird und sich Änderungen ergeben.

Server-seitige Skripte[Am Gwëntext werkeln]

Automatisch eingebundene Standardskripte der deutschsprachigen Wikipedia waren bislang:

Damit wurde bis Februar 2011 die Seiten-Funktionalität unterstützt. Mit Ausnahme von MediaWiki:*.js wird dies aber nach und nach durch das mediaWiki-Objekt sowie jQuery ersetzt. Der jeweils aktuelle Stand der Einbindung muss überprüft werden. Übergangsweise werden legacy-Funktionen des ResourceLoader verfügbar sein (Schau aa mw:).

Um historische Skripte nachvollziehen zu können, sind auch veraltete Server-Skripte aufgeführt. Sie sind teilweise noch abrufbar, werden aber nicht zwangsläufig in die Seiten eingebunden.

  1. Nicht angemeldete Benutzer können Greasemonkey nutzen.
  2. Das hängt mit unterschiedlichen Möglichkeiten im Fall von JS-Seiten zusammen: Um die (nur schwarz-weiße) Vorschau zu generieren, kann der Abschnitt der HTML-Seite spontan ersetzt werden durch
    "<pre>" + htmlescape(TEXTAREA-Inhalt) + "</pre>"
    • Man braucht also den Server nicht zu kontaktieren; die submit-Anfrage wird nicht abgeschickt. Damit bleibt in der Seite auch der ursprüngliche Inhalt des TEXTAREA zu Beginn der Bearbeitung hinterlegt. Dann kann man aber den ursprünglichen und momentanen Inhalt von TEXTAREA lokal miteinander vergleichen und nur den entsprechenden Abschnitt der HTML-Seite durch das Diffpage-Resultat ersetzen, ohne die Seite neu abzurufen.
    Damit werden aber die Standard-Skripte (und ggf. weitere) nur einmalig eingebunden; spätere Änderungen daran wirken sich nicht aus, und sie werden nicht mehr aktiv ausgeführt.
    Die Art der Umsetzung kann spontan und von Wiki-Projekt zu Wiki-Projekt wechseln.
  3. Upgrade guide Core 3.0. jquery.com
  4. mozilla.com
  5. Vom NoScript-AddOn für Firefox wird dies aus Sicherheitsgründen standardmäßig blockiert; die Einstellung noscript.allowURLBarJS muss mittels about:config auf true gesetzt werden.
  6. Seit Firefox 6 ist die Eingabe von JS-Code in der URL-Adresszeile komplett deaktiviert. Auch die Option browser.urlbar.filter.javascript bleibt wirkungslos.
  7. Mitunter kommt es nach Aktivierung zu seltsamem Verhalten der Seite im Browser, vor allem zu ihrer fast vollständigen Leerung. In diesem Fall ist an die letzte Anweisung anzuhängen: ;void(0)