HTML/Tutorials/Links/Bookmarklets
Dieser Artikel greift einen SELFHTML-Aktuell Artikel von Robert Bienert aus dem Jahr 2006 auf.[1] Der Bedarf für Bookmarklets mag zurückgegangen sein, für manche Zwecke sind sie aber nach wie vor nützlich – und sie bergen auch Gefahren.
Inhaltsverzeichnis
Was ist ein Bookmarklet oder Favelet?
Ein Bookmarklet ist nichts weiter als eine URL, die das Schema javascript: verwendet. Rufst du mit einem Browser eine solche URL ab, wird das darin befindliche JavaScript ausgeführt.
Solche URLs können vom Benutzer als Lesezeichen (Favorit, daher Favelet) im Browser abgespeichert werden und dann auf jeder beliebigen Webseite ausgeführt werden. Als Lesezeichen gespeicherte Bookmarklets entstehen normalerweise durch eine bewusste Aktion des Nutzers und sind deshalb üblicherweise vertrauenswürdig.
Sie können aber auch im href-Attribut eines <a> oder <area>-Elements stecken und beim Klick auf den entsprechenden Link ausgeführt werden. Webseiten, die Inhalte ihrer Besucher präsentieren (z.B. Foren oder Blogs mit Kommentarfunktion) müssen deshalb aufpassen, solche URLs nicht zuzulassen.
Was können Bookmarklets?
Das Skript in einem Bookmarklet kann alles tun, was auch JavaScript auf der Webseite tun kann: das DOM manipulieren, globale Variablen verändern und auch Ressourcen abrufen, soweit Same-Origin-Regeln, CORS und Content Security Policy das zulassen. Dieser Artikel kann und soll jedoch keine JavaScript-Einführung sein, er soll nur die grundsätzlichen Mechanismen und Restriktionen darstellen. Die Kenntnis von JavaScript und der Möglichkeiten, die es im Browser hat, wird deshalb vorausgesetzt.
<a href="javascript:alert('You have been pwned!');">Impressum</a>
Einige der Restriktionen für HTTP-URLs gelten hier nicht, beispielsweise müssen die Leerstellen nicht als %20 codiert werden (und dürfen auch nicht – wie es bei Webservern möglich ist – durch ein + ersetzt werden). Acht geben musst du allerdings bei Anführungszeichen, Sonderzeichen und Zeichenfolgen, die im HTML-Attribut selbst eine Bedeutung haben.
Eines der Beispiele von Robert Bienert zeigte, wie sich mit einem Bookmarklet Inhalte von Browservariablen anzeigen lassen:
<a href="javascript:alert(navigator.useragent);">User-Agent anzeigen</a>
Das funktioniert auch heute noch, im Allgemeinen würdest du hierfür aber die Entwicklerwerkzeuge des Browsers öffnen und dort die Konsole benutzen. In Browsern, die keine Entwicklerwerkzeuge haben (Mobilgeräte), ist diese Methode für eine schnelle Abfrage immer noch anwendbar.
Das Einzige, was du in einem Bookmarklet nicht verwenden kannst, sind Zeilenumbrüche. Das Skript muss sich auf einer einzigen Codezeile befinden. Technisch ist das kein Problem, JavaScript erlaubt einzeilige Programme beliebiger Komplexität. Für die Bearbeitung des Bookmarklets ist dies jedoch ein gravierendes Hindernis. Wenn du ein längeres Bookmarklet erstellen und pflegen möchtest, erstellst du es wie ein normales Skript und verwendest ein Minifizierungstool, mit dem du das Script in eine Codezeile komprimieren kannst. Oder lade das Bookmarklet als Script nach, wie im Abschnitt „Größere Bookmarklets“ dargestellt.
Viele Beispiele für Bookmarklets möchten mit window.open Fenster öffnen. Die Pop-up-Blocker der heutigen Browser verhindern allerdings zumeist das Öffnen von Fenstern, deren URL einen anderen Origin als die dargestellte Seite hat. Deshalb soll auf solche Beispiele verzichtet werden.
Wovon dringend abgeraten werden soll, ist das Erstellen eines Login-Bookmarklets, das automatisch Login-Daten in ein Anmeldeformular einträgt. So etwas geht natürlich, du musst lediglich die entsprechenden Eingabefelder suchen und die Werte dort eintragen, aber damit hast du deine Login-Daten ungeschützt in einem Bookmarklet liegen. Gerät dieses in falsche Hände, sind die Login-Daten verloren. Verwende dafür einen Login-Manager, den der Browser mitliefert oder einen Passwortmanager, den du separat installierst.
Vorsicht, Rückgabewert!
Die Ausführung von JavaScript-Anweisungen kann einen Wert ergeben. Das folgende Bookmarklet hat beispielsweise den Wert „Self“:
javascript:alert("Selfhtml");window.zzz="Self";
Wenn ein Bookmarklet eine Zeichenkette zurückgibt, wird diese vom Browser als neues, anzuzeigendes Dokument aufgefasst. Das kann Absicht sein, oder auch nicht. Du könntest ein Bookmarklet erstellen, das von einem Server ein HTML-Dokument lädt und dieses zurückgibt. Es könnte aber auch nur ein Versehen sein.
undefined:
javascript:{alert("Selfhtml"); window.zzz="Self";}Größere Bookmarklets
Um das Minifizieren des Bookmarklets auf eine Zeile zu vermeiden, oder um Bookmarklets nutzen zu können, die länger sind als eine URL sein sollte, kannst du das Script auch aus einer externen JS-Datei nachladen. Dazu erstellst du ein <script>-Element mit einem src-Attribut und fügst es dem DOM hinzu. Der Browser lädt daraufhin das Script und führt es aus.
Hierbei sind einige Aspekte zu beachten. Vor allem solltest du vermeiden, ein solches Script mehrfach zu laden. Das Script sollte deshalb ein Element im DOM oder eine Eigenschaft im window-Objekt hinterlassen, über die das Script erkannt und bei wiederholtem Bookmarklet-Aufruf neu gestartet werden kann. Damit das eigentliche Bookmarklet keinen „Fußabdruck“ im Browser hinterlässt, sollte es als IIFE gestaltet sein. Das Bookmarklet sähe als übersichtliches Script so aus:
(function() {
const url = "https://example.org/scripts/favelet3.js",
myself = document.head.querySelector(`script[src=${url}]`);
if (myself)
myself.restart();
else
document.head.insertAdjacentHTML("beforeend", `<script type='module' src='${url}'>`);
})();
Die einzeilige Bookmarklet-Darstellung wäre hingegen diese (das Wiki bricht es um). Bis auf den Namen des Scripts ist dieses Loader-Bookmarklet generisch und kann als Vorlage verwendet werden.
(function(){const url="https://example.org/scripts/favelet3.js",myself=document.head.querySelector(`script[src=${url}]`);myself? myself.restart():document.head.insertAdjacentHTML("beforeend",`<script type='module' src='${url}'>`);})();
Dieses Bookmarklet sucht im DOM das Script-Element, das es beim ersten Aufruf erstellt hat, und geht davon aus, dass es an diesem Script-Element eine Methode restart gibt. Das geladene Script hat die Aufgabe, sein eigenes <script>-Element zu finden und die restart-Methode dort hinzuzufügen. Auf diese Weise bleibt der Ablauf isoliert und benötigt keine globalen Variablen.
Ein weiterer Punkt ist, dass das externe Script einen minimalen „Fußabdruck“ im Browser hinterlassen muss, damit es die Seite, in der es ausgeführt wird, nicht stört. Schlimmer noch: Es gibt Seiten, die zu erkennen versuchen, ob der Anwender Bookmarklets ausführen will oder auf andere Weise Dinge ins DOM injiziert. Vor einer solchen Erkennung möchte man sich natürlich – soweit möglich – verbergen.
Deshalb wird das externe Script mit type="module" geladen, damit seine Variablen vom übrigen Dokument isoliert bleiben. Hinzu kommt, dass ein ES6-Modul über import.meta.url auf die URL zugreifen kann, von der es geladen wurde, so dass man das Script-Element, über das das Script geladen wurde, finden kann, ohne die URL im Script hinterlegen zu müssen.
Das externe Skript kann sich am folgenden Muster orientieren:
const myself = findMyself();
if (myself != null) {
initialize();
}
function initialize() {
// Initialisierung durchführen
...
// Restart-Funktion am Script-Element hinterlegen
myself.restart = moduleAction;
// Eigentliche Bookmarklet-Aktion durchführen
moduleAction();
}
function moduleAction() {
console.log("Bookmarklet wird ausgeführt");
}
function findMyself() {
for (const scriptElem of document.head.querySelectorAll("script")) {
if ((new URL(scriptElem.src)).href == import.meta.url)
return scriptElem;
}
return null;
}
Wie du das Skript genau aufbaust, hängt von deinen Anforderungen ab. Was als Initialisierung zu geschehen hat, ob die Restart-Aktion auch beim erstmaligen Aufruf durchzuführen ist oder etwas anderes passieren soll, das kann ein allgemeiner Rahmen nicht vorgeben. Du kannst das DOM der Seite manipulieren, Stylesheets ergänzen oder manipulieren, du kannst auch „nach Hause telefonieren“, also Webservices auf Ihrem eigenen Server aufrufen. Aber nur dort. Auf dem Server, von dem die eigentliche Seite geladen wurde, kannst du nur Services aufrufen, wenn das dort mittels CORS freigeschaltet wurde.
Das Muster zeigt vor allem, wie das externe Script das <script>-Element findet, mit dem es geladen wurde, und daran die restart-Aktion hinterlegt. Dazu muss das Script eine Möglichkeit haben, dieses Element zu identifizieren. Das kann über eine ID geschehen, die dann aber im Bookmarklet und im externen Script bekannt sein muss. Wir verwenden hier die import.meta-Pseudovariable, die seit 2018 in den Evergreen-Browsern zu finden ist und aus der man die URL erfahren kann, mit der das Script geladen wurde. Willst du import.meta nicht nutzen, weil du auch Altbrowser supporten musst, dann vergib eine ID oder trage den Namen des Scripts als Konstante im Script ein.
Referenzen
- ↑ Selfhtml-Aktuell 2006: Robert Bienert, Bookmarklets, archiviert von archive.org