Opera Extensions und XHR
Zur Zeit schreibe ich an einer Opera Extension. Bisher zu finden auf GitHub, später möchte ich sie dann auch im Katalog von Opera einstellen. Das als Vorgeschichte.
Situation
Das JavaScript-Skript der Extension wird auf der besuchten Seite eingebunden. Sobald der Mauscursor einen Link berührt, wird geprüft, ob das Ziel ein Bild (endend auf .JPG, .PNG etc.) ist und falls dem so ist, angezeigt. Führt der Link nicht direkt zu einer Grafik, aber zu einem Dienst mit einer verfügbaren API, wird diese gefragt. Einige Dienste, die sich auf Medien wie Grafiken und Videos spezialisiert haben, bieten den oEmbed-Standard an; so auch deviantART.
Ein Beispiel, wie das abläuft:
- Folgender Link wird gesetzt: http://fav.me/d468yca. Dabei haben wir keinen direkten Link zur Grafik selbst, aber zu dessen Galerie-Seite.
- Um die URL für die Grafik selbst herauszufinden, fragen wir die API des Dienstes – in diesem Fall deviantART: http://backend.deviantart.com/oembed?url=http://fav.me/d468yca.
- Die Antwort enthält den Pfad zur Grafik:
"url":"http://fc02.deviantart.net/fs70/i/2011/223/0/4/cloudsdale_by_karzahnii-d468yca.png"
Komplikation
„Dann ist doch alles fein, oder nicht?“ Leider nicht ganz, es gibt eine kleine Hürde. Dafür schauen wir uns kurz an, wie die Anfrage als Code aussieht.
var url = "http://backend.deviantart.com/oembed?url=http://fav.me/d468yca"; var response; var xhr = new XMLHttpRequest(); xhr.open( "GET", url, false ); xhr.onreadystatechange = function() { if( this.readyState != 4 ) { return; } if( this.status == 200 && this.responseText ) { response = this.responseText; } }; xhr.send();
Es soll ein XMLHttpRequest (XHR) an die Seite gesendet werden. Nun wird dieser Code ausgeführt und die Antwort … ist leer? Die Fehlerkonsole (Strg+Shift+O) gibt einen Hinweis.
Event thread: message
Uncaught exception: ReferenceError: Security violation
Um die Katze gleich aus dem Sack zu lassen – das Hindernis hier ist die Same Origin Policy. Long story short: XHR funktioniert nur, wenn die Absenderseite das gleiche Protokoll, den gleichen Host und den gleichen Port hat wie die Empfängerseite. Aber unser Skript soll von jeder Seite aus funktionieren!
Lösung
Die Grafik zeigt die Verzeichnisse und Dateien in meiner Extension. In der obligatorischen config.xml
muss diese Zeile eingetragen werden:
<access origin="http://backend.deviantart.com" />
Das access
-Element ist bei dev.opera.com dokumentiert. Dies erlaubt der Erweiterung den seitenübergreifenden Zugriff. Allerdings nur aus dem Hintergrund-Prozess heraus. Dabei handelt es sich um die Datei index.html
, die wiederum scripts/background.js
einbindet. Alle Dateien in includes/
werden als Inline-Skripte auf der besuchten Seite ausgeführt. Bei XHR reichen wir die Daten daher an den Hintergrund weiter:
function getRemoteContent( getUrl ) { opera.extension.postMessage( { url: getUrl } ); }
In background.js
reagieren wir folgendermaßen:
opera.extension.onmessage = function( event ) { if( typeof event.data.url != "undefined" ) { var response = doXHR( event.data.url ); event.source.postMessage( response ); } }; function doXHR( url ) { ... }
Im Skript, von dem die Anfrage ausging, können wir eine Funktion registrieren, die Nachrichten vom Hintergrundprozess entgegennimmt. In unserem Beispiel wären dies die Antworten der Seiten auf unser XHR.
function handleRemoteContent( event ) { var data = event.data; ... } opera.extension.onmessage = handleRemoteContent;
In anderen Worten: Yeehaw, Ziel erreicht!
Embed deviantART media in your apps and websites with oEmbed – deviantart.com
Same origin policy for JavaScript – developer.mozilla.org
Accessing an Opera extension's background process – dev.opera.com
config.xml – dev.opera.com