How To: JavaScript-Module (*.jsm) und clipboard-API mit dem Add-on SDK

Im ersten Tutorial zum Add-on SDK von Firefox haben wir eine Einführung in die Add-on-Erstellung erhalten und bereits verschiedene APIs genutzt: Wir haben gelernt, Funktionen in der Add-on Leiste von Firefox hinzuzufügen (Widget-API) und Seiten auf verschiedene Weisen zu öffnen (Tabs-API). Durch die Self-API waren wir in der Lage, Dateien mit unserer Erweiterung mitzuliefern und schließlich konnten wir durch das preferences-service-Modul auch noch Entscheidungen in Abhängigkeit von about:config-Schaltern treffen. Im zweiten Tutorial habe ich gezeigt, wie man Zugriff auf das Components-Objekt von Firefox erhält, welches uns Zugriff auf sämtliche XPCOM-Interfaces gibt, was beinahe unbegrenzte Möglichkeiten eröffnet.

Heute lernen wir eine weitere Möglichkeit kennen, Komponenten von Firefox zu nutzen, nämlich in Form von JavaScript-Modulen (*.jsm). Außerdem lernen wir noch eine weitere API kennen: die Clipboard-API, um Inhalte in die Zwischenablage zu kopieren.

Beginnen wir direkt mit den JavaScript-Modulen. Wir erinnern uns an das letzte Tutorial zurück, in welchem wir folgenden Code benutzt haben, um die Datei application.ini aus dem Installationsverzeichnis von Firefox zu laden, um diese anschließend durch einen INI-Parser auszulesen:

var applicationFile = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties).get('CurProcD', Ci.nsIFile);
applicationFile.append('application.ini');

XPCOM stellt einen mächtigen Weg dar um Zugriff auf sehr viel Firefox-Funktionalität zu erhalten, vieles ist stattdessen aber auch als JavaScript-Modul implementiert. Diese Aufgabe können wir sowohl über den XPCOM-Weg als auch über den JSM-Weg lösen. Die Dokumentation hatte uns an dieser Stelle bereits eine alternative Möglichkeit über ein solches JavaScript-Modul, nämlich FileUtils.jsm, vorgeschlagen:

let FileUtils = Cu.import('resource://gre/modules/FileUtils.jsm').FileUtils;
var applicationFile = FileUtils.getFile('CurProcD', ['application.ini']);

Tauschen wir diese beiden Zeilen aus, funktioniert unsere Erweiterung ganz genau so wie vorher. Die JSM-Version ist nicht nur schöner zu lesen, sondern auch vorzuziehen, sofern möglich.

Das als kleine Einführung. Wir wollen für unser nächstes kleines Erweiterungsprojekt nämlich ein JavaScript-Modul nutzen. Zuerst wieder die Konzeptionelle Frage: Was wollen wir machen? Dieses mal bauen wir eine leicht veränderte Version meiner Erweiterung Copy Extensions to Clipboard. Diese Erweiterung erzeugt wieder ein Symbol in der Add-on Leiste von Firefox. Bei Klick auf dieses wird eine Auflistung aller installierten Erweiterungen in die Zwischenablage kopiert. Dazu beginnen wir wieder mit einem Grundgerüst, welches wir dieses mal extensions taufen und die (noch) leere Methode copyExtensions() beinhaltet. Außerdem erstellen wir gleich unser Icon für die Add-on Leiste, welches diese Methode bei Klick aufruft, und laden über das [+]-Icon unter Data ein passendes Icon dazu hoch. Nachdem die Widget-API bereits Teil beider vorangegangener Tutorials war, erspare ich mir hierzu weitere Kommentare:

const self = require('self');

var extensions = {
copyExtensions : function() {

}
}

exports.main = function() {
var widget = require('widget').Widget({
id : 'extensions',
label : 'copy extensions to clipboard',
contentURL : self.data.url('extension.png'),
onClick : function() {
extensions.copyExtensions();
}
});
};

Um auf alle installierten Erweiterungen zugreifen zu können, benutzen wir das AddonManager.jsm-Modul. Dieses importieren wir auf die gleiche Weise wie obiges Beispiel. Praktischerweise zeigt die Dokumentation wieder direkt den Code, den wir brauchen und auch gleich mit getAllAddons() die richtige Methode.

copyExtensions : function() {
let AddonManager = Cu.import('resource://gre/modules/AddonManager.jsm').AddonManager;

AddonManager.getAllAddons(function(aAddons) {

});
}

Dabei vergessen wir natürlich zu Beginn des Scripts nicht:

const {Cu} = require('chrome');

Nun bedarf es einer kurzen Überlegung: Wir wollen Informationen zu jeder installierten Erweiterung haben. Und diese wollen wir in die Zwischenablage kopieren. Über getAllAddons() erhalten wir Informationen zu allen installierten Erweiterungen, das Array aAddons beinhaltet dabei gemäß Dokumentation alle Add-on-Objekte. Wir können also über dieses Array iterieren und mit den Informationen etwas anstellen. In jedem Fall brauchen wir am Ende etwas vom Typ String, was wir in die Zwischenablage kopieren können (kleine Vorwegname zur clipboard-API). Die Idee ist es, die Informationen zu jeder Erweiterung in einen String zu verpacken und damit ein Array aufzufüllen, welches die Informationen zu jeder Erweiterung beinhaltet. Anschließend werden wir aus diesem Array wieder einen einzigen String machen, so dass wir die Informationen in die Zwischenablage kopieren können.

Als erstes erstellen wir dazu in der getAlladdons()-Methode ein neues Array extensionList für unsere Erweiterungen. In einer for-Schleife weisen wir einer addon-Variablen das jeweilige Add-on-Objekt zu und erstellen außerdem noch eine Variable für den dazugehörigen Inhalt, welchen wir später kopieren möchten. Über addon.eigenschaft können wir dann auf die verschiedenen Informationen zu den Erweiterungen zugreifen. Die möglichen Eigenschaften stehen hier. Wir entscheiden uns für den Namen und die Version der Erweiterung. Schließlich füllen wir mit diesem Eintrag in jedem Schleifendurchlauf unser Array mit extensionList.push(entry). Und weil wir an dieser Stelle testen möchten, ob wir bis hierhin alles richtig gemacht haben, machen wir eine Konsolen-Ausgabe. Auch hierfür benötigen wir einen String. Dazu können wir außerhalb der Schleife console.error(extensionList.toString()) verwenden. Doch wenn wir das jetzt testen, werden wir feststellen, dass es zwar funktioniert, aber alle Einträge komma-seperiert nebeneinander stehen. Schöner wäre es da doch, wenn alles untereinander stünde. Statt extensionList.toString() verwenden wir stattdessen extensionList.join(‘\r\n’). Wichtig ist dabei, dass man nicht nur \n verwendet, da im Windows Editor nach dem Kopieren später sonst trotzdem alles nebeneinander stehen wird. Was wir gerade gemacht haben:

AddonManager.getAllAddons(function(aAddons) {
var extensionList = [];

for(var i in aAddons) {
var addon = aAddons[i];
var entry = '';

entry = addon.name + ', Version: ' + addon.version;

extensionList.push(entry);
}

console.error(extensionList.join('\r\n'));
});

Die Reihenfolge der Erweiterungen ist noch ziemlich wild. Wir wollen unsere Erweiterungen alphabetisch sortieren. Dies machen wir, indem wir vor der Ausgabe noch folgendes einfügen: extensionList.sort(). Hat man Erweiterungen installiert, welche sowohl mit kleinen als auch mit großen Buchstaben beginnen, fällt es vielleicht an dieser Stelle auf: Die Erweiterungen sind zwar nun alphabetisch sortiert, aber case-sensitive, das bedeutet, dass erst alle Erweiterungen kommen, welche mit einem Großbuchstaben beginnen, anschließend erst alle, welche mit einem Kleinbuchstaben beginnen. Um korrekt alphabetisch zu sortieren, müssen wir in den Sortier-Algorithmus eingreifen und der sort()-Funktion noch eine Vergleichsfunktion mitgeben. Die Vergleichsfunktion muss einen Wert kleiner null, null oder größer null zurückliefern, je nachdem, ob der zweite Parameter der Vergleichsfunktion größer, gleich oder kleiner dem ersten ist, wobei die Parameter den Zeichen entsprechen, welche miteinander verglichen werden. Dazu verwenden wir die toLowerCase()-Funktion, um beide Buchstaben sicher als Kleinbuchstaben zu erhalten und überprüfen, ob die beiden Kleinbuchstaben gleich oder ungleich sind. Sind sie ungleich, wird -1 zurückgegeben, wenn der erste kleiner ist, und 1, wenn der zweite kleiner ist. Sind die Kleinbuchstaben hingegen miteinander identisch, wird überprüft, ob die originalen Buchstaben identisch sind und in diesem Fall eine 0 zurückgegeben und ansonsten wieder -1 oder 1, je nachdem, welcher Buchstabe kleiner oder größer ist, verglichen mit den originalen Buchstaben. Dies mag jetzt erst einmal kompliziert klingen, ist es aber gar nicht, wenn man den Code einmal genauer ansieht:

extensionList.sort(function(a, b) {
var al = a.toLowerCase(), bl = b.toLowerCase();

if(al == bl) {
if(a == b) {
return 0;
}
else {
if(a < b)
return -1;
else
return 1;
}
}
else {
if(al < bl)
return -1;
else
return 1;
}
});

Und wer es lieber kompakt mag, kann dies auch kürzer schreiben:

extensionList.sort(function(a, b) {
var al = a.toLowerCase(), bl = b.toLowerCase();

return al == bl ? (a == b ? 0 : a < b ? -1 : 1) : al < bl ? -1 : 1;
});

Wenn wir unser Add-on nun noch einmal testen, stimmt nun auch die Reihenfolge der Erweiterungen!

Nun geben wir vor dem Versionsnamen noch den Typ der Erweiterung an und ggf. ob die Erweiterung gerade deaktiviert ist, dafür überprüfen wir addon.type und addon.isActive. Die Zeile mit den Add-on-Informationen, die wir schon im Code stehen haben, müssen wir dann noch leicht anpassen und aus dem = ein += machen, damit wir den String nur ergänzen und nicht überschreiben:

switch(addon.type) {
case 'extension':
entry = '[extension]';
break;
case 'plugin':
entry = '[plugin]';
break;
case 'theme':
entry = '[theme]';
break;
case 'user-script':
entry = '[user-script]';
break;
case 'userstyle':
entry = '[userstyle]';
break;
default:
entry = '[unknown]';
}

if(!addon.isActive)
entry += '[DISABLED]';

entry += ' ' + addon.name + ' ' + addon.version;

Wir könnten den addon.type auch direkt verwenden und uns diesen ganzen switch-Block sparen. Der Grund dafür, dass wir es so vermeintlich umständlich machen, ist der, dass wir die Erweiterung für das nächste Tutorial weiterverwenden wollen. Dort lernen wir dann, wie man mit dem SDK erstellte Erweiterungen in mehrere Sprachen lokalisiert. :)

Jetzt werden wir die Konsolen-Ausgabe durch ein Kopieren in die Zwischenablage ersetzen. Dafür binden wir als erstes die clipboard-API ein:

const clipboard = require('clipboard');

Nun ersetzen wir console.log() nur noch durch clipboard.set() und schon steht der Inhalt, welcher vorher in die Konsole geschrieben wurde, in der Zwischenablage! Wir erweitern das Ganze jetzt noch, indem wir den Inhalt zusätzlich in einem neuen Tab noch anzeigen. Dafür schreiben wir unter dem clipboard.set() noch:

const tabs = require('tabs').open('data:text/plain;charset=utf-8,' + extensionList.join('%0A'));

Die Tabs-API kennen wir ja schon, eine kurze Erklärung ist trotzdem notwendig. Mit der open()-Methode öffnen wir einen neuen Tab und können durch das data:text/plain Text direkt über die Adresszeile mitgeben, welchen wir in Firefox anzeigen. Das charset=utf-8 ist notwendig, damit Umlaute und Sonderzeichen korrekt dargestellt werden. Außerdem fällt auf, dass wir hier auch wieder extensionList.join() verwenden, allerdings übergeben wir als Parameter hier nicht ‘\r\n’, sondern ‘%0A’. Das hat den Grund, dass wir den String über die Adressleiste übergeben, dort werden neue Zeilen auf diese Weise dargestellt, ‘\r\n’ wäre an dieser Stelle wirkungslos.

Der komplette Code:

const {Cu} = require('chrome');
const clipboard = require('clipboard');
const self = require('self');

var extensions = {
copyExtensions : function() {
let AddonManager = Cu.import('resource://gre/modules/AddonManager.jsm').AddonManager;

AddonManager.getAllAddons(function(aAddons) {
var extensionList = [];

for(var i in aAddons) {
var addon = aAddons[i];
var entry = '';

switch(addon.type) {
case 'extension':
entry = '[extension]';
break;
case 'plugin':
entry = '[plugin]';
break;
case 'theme':
entry = '[theme]';
break;
case 'user-script':
entry = '[user-script]';
break;
case 'userstyle':
entry = '[userstyle]';
break;
default:
entry = '[unknown]';
}

if(!addon.isActive)
entry += '[DISABLED]';

entry += ' ' + addon.name + ' ' + addon.version;

extensionList.push(entry);
}

extensionList.sort(function(a, b) {
var al = a.toLowerCase(), bl = b.toLowerCase();

return al == bl ? (a == b ? 0 : a < b ? -1 : 1) : al < bl ? -1 : 1;
});

clipboard.set(extensionList.join('\r\n'));
const tabs = require('tabs').open('data:text/plain;charset=utf-8,' + extensionList.join('%0A'));
});
}
}

exports.main = function() {
var widget = require('widget').Widget({
id : 'extensions',
label : 'copy extensions to clipboard',
contentURL : self.data.url('extension.png'),
onClick : function() {
extensions.copyExtensions();
}
});
};

Firefox Add-on: Copy Extensions to Clipboard 2.0

Mit Copy Extensions to Clipboard habe ich eine kleine Erweiterung geschrieben, welche in der Add-on Leiste ein weiteres Symbol hinzufügt, über welches man per Mausklick eine komplette Auflistung aller installierten Add-ons, Plugins, Themes, Userscripts (Greasemonkey) sowie Userstyles (Stylish) in die eigene Zwischenablage ablegen kann. Diese Liste kann anschließend direkt und ohne Umwege in ein Textprogramm oder ein Forum – beispielsweise aus Support-Gründen bei Problemen mit Firefox – kopiert werden. Zusätzlich wird die Auflistung auch direkt im Browser angezeigt.

Die Auflistung beinhaltet neben dem Art der Erweiterung und deren Name auch die Versionsangabe, die Add-on ID sowie eine Angabe, ob die Erweiterung im aktuellen Profil, einem anderen userspezifischen Ort oder global im System installiert ist.

Die Erweiterung erfordert zur Installation keinen Neustart des Browsers, da sie mit dem Add-on SDK erstellt wurde. Derzeit unterstützt die Erweiterung sowohl die deutsche als auch die englische Sprache.

How To: XPCOM-Zugriff und INI-Parser mit dem Firefox Add-on SDK

Vor einigen Monaten hatte ich bereits ein erstes Tutorial geschrieben, welches eine kleine Einführung in die Add-on-Entwicklung für Firefox mit dem Add-on SDK gegeben hat. In diesem haben wir gelernt, Funktionen in der Add-on Leiste von Firefox hinzuzufügen (Widget-API) und Seiten auf verschiedene Weisen zu öffnen (Tabs-API). Durch die Self-API waren wir in der Lage, Dateien mit unserer Erweiterung mitzuliefern und schließlich konnten wir durch das preferences-service-Modul auch noch Entscheidungen in Abhängigkeit von about:config-Schaltern treffen.

Hierauf möchte ich nun aufbauen und einen Schritt weiter gehen. Wir können auch direkten Zugriff auf das Components-Objekt von Firefox erhalten, welches uns Zugriff auf sämtliche XPCOM-Interfaces gibt, was beinahe unbegrenzte Möglichkeiten eröffnet. So werden wir heute lernen, Applikations-Informationen wie die Versionsnummer von Firefox auszulesen und schließlich eine *.ini-Datei aus dem Installationsverzeichnis von Firefox auslesen. Am Ende des Tutorials werden wir wieder eine kleine Erweiterung erstellt haben.

Die erste Überlegung ist eine Konzeptionelle, die nach dem Sinn unserer Erweiterung. Und hier möchte ich die Idee meiner Erweiterung Current Pushlog aufgreifen und die Erstellung einer vereinfachten Version erklären. Dazu muss man wissen, dass Mozilla ein Versionskontrollsystem (Mercurial) nutzt, über welches sich alle Änderungen von einer Firefox zur nächsten nachvollziehen lassen. Diese Änderungen zwischen zwei Versionen sind in sogenannten Pushlogs zusammengefasst und den jeweils passenden Link wollen wir über unsere Erweiterung bereitstellen. Der Link zu dieser Seite entspricht einem festen Schema und hierfür benötigen wir das Quellverzeichnis sowie die Bezeichnung des sogenannten Changesets sowohl von der aktuellen als auch der vorherigen Version.

Der erste Schritt ist wie immer das Erstellen eines neuen Projekts im Add-on Builder von Mozilla. In den Projekteinstellungen geben wir der Erweiterungen einen sinnvollen Namen sowie eine Beschreibung. Dann starten wir mit einem ersten Grundgerüst der Erweiterung, welches erst einmal noch nichts weiter macht, als eine init()-Funktion bereitzustellen, in welcher wir über console.error() Text in die Konsole schreiben, welche wir per Klick auf das Symbol mit dem Warndreieck im Add-on Builder öffnen können. Diese Funktion rufen wir schließlich in der main()-Funktion auf. Alles, was innerhalb von exports.main = function() {} geschrieben steht, wird ausgeführt, sobald die Erweiterung bereit ist.

var pushlog = {
init : function() {
console.error('Wir starten ein neues Addon.');
}
}

exports.main = function() {
pushlog.init();
};

Wirklich spannend wird es jetzt: Wir wollen Informationen aus Firefox auslesen. Als kleine Aufwärmübung lesen wir ein paar Anwendungsinformationen zu Firefox aus. Hierfür brauchen wir chrome-Privilegien und das erreichen wir auf ganz ähnliche Weise, wie wir APIs einbinden, wie wir bereits im letzten Tutorial gelernt haben. Wir schreiben in die erste Zeile unserer main.js:

const {Cc, Ci} = require('chrome');

Das Cc steht hierbei für Components.classes und das Ci für Components.interfaces. Was wir hier schreiben, hängt davon ab, was wir benötigen und was wir benötigen, erfahren wir aus der Dokumentation in dem Moment, indem wir den Code schreiben. Eine Übersicht über die möglichen Symbole gibt es hier.

Die Übersicht der vorhandenen XPCOM-Interfaces hatte ich weiter oben bereits verlinkt, dies sei an dieser Stelle noch einmal getan. Oft lässt sich beim Überfliegen der Namen erahnen, wo sich ein näherer Blick lohnen könnte. Das Ziel ist wie gesagt die Ausgabe von Anwendungsinformationen – da klingt nsIXULAppInfo doch gut! Hier sehen wir bereits gleich zu Beginn ein Beispiel, wie wir dieses Interface verwenden können:

var xulAppInfo = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULAppInfo);

Im Prinzip können wir das genau so in unsere Erweiterung übernehmen. Und das machen wir auch, Components.classes ersetzen wir dabei noch durch Cc und Components.interfaces durch Ci und übernehmen das dann anstelle der bisherigen console.error()-Anweisung in unsere init()-Methode. Und deswegen haben wir uns für diese Kürzel auch in der require()-Anweisung in der ersten Zeile entschieden. Über xulAppInfo.version beispielsweise können wir dann auf die Versionsnummer zugreifen, die möglichen Attribute stehen in der Dokumentation direkt darunter.

init : function() {
var xulAppInfo = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULAppInfo);
console.error(
'version: ' + xulAppInfo.version + '\n'
+ 'platformVersion: ' + xulAppInfo.platformVersion + '\n'
+ 'ID: ' + xulAppInfo.ID + '\n'
+ 'name: ' + xulAppInfo.name + '\n'
+ 'vendor: ' + xulAppInfo.vendor + '\n'
+ 'platformBuildID: ' + xulAppInfo.platformBuildID
);
}

Speichern wir jetzt unsere Erweiterung und testen sie, erhalten wir in der Fehlerkonsole diverse Informationen zu unserem Firefox-Build und haben damit bereits erfolgreich auf ein XPCOM-Interface zugegriffen! Aber das soll nur zur Aufwärmung gewesen sein, für unsere gewünschte Erweiterung ist dies nicht weiter relevant, hierfür brauchen wir ein anderes Interface. Also löschen wir den kompletten Inhalt der init()-Methode (aber nicht die Methode selber, die brauchen wir später noch).

Die Informationen, die wir brauchen, liefert Firefox in einer Datei, nämlich in der Datei application.ini, welche sich im Installationsverzeichnis von Firefox befindet. Hierzu bietet die Dokumentation einen schönen Artikel zu File I/O, im Abschnitt “Getting special files” steht der entscheidende Code, um auf diese Datei zuzugreifen. An den entsprechenden Stellen schreiben wir wieder Cc und Ci und dann gibt es noch eine weitere Stelle aus dem Beispiel anzupassen, nämlich das “ProfD”. Dies bedeutet nämlich, dass wir die Datei im Profilverzeichnis suchen. Direkt unter dem Code-Beispiel steht aber bereits eine Tabelle mit allen möglichen Orten. Und da wir das Installationsverzeichnis von Firefox wollen, entscheiden wir uns an dieser Stelle für “CurProcD”. Diese ganze Kette haben wir einer Variablen zugeordnet und auf diese wenden wir die append()-Methode mit dem Namen der Datei als Parameter an, welche wir auslesen möchten, nämlich application.ini. Diesen ganzen Code schreiben wir in eine neue Methode, welche wir getApplicationValue(section, key) nennen. Die beiden Parameter werden wir gleich noch brauchen. Hier der Code bis zu dieser Stelle:

init : function() {
},

getApplicationValue : function(section, key) {
var applicationFile = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties).get('CurProcD', Ci.nsIFile);
applicationFile.append('application.ini');
}

Schauen wir uns an dieser Stelle den Aufbau der application.ini (Ausschnitt) an:

[App]
Version=10.0.2
BuildID=20120215223356
SourceRepository=http://hg.mozilla.org/releases/mozilla-release
SourceStamp=72ad46d416ce

[Gecko]
MinVersion=10.0.2
MaxVersion=10.0.2

Und von hier kommen wir direkt zu den Parametern unserer getApplicationValue()-Methode – bei [App] und [Gecko] handelt es sich um die “section”, alles links der Gleichheitszeichen sind die “keys”, welche wir auslesen wollen. Was wir konkret auslesen möchten, sind SourceRepository und SourceStamp, beides aus der section [App]. Dafür erstellen wir nun erst einmal zwei weitere Methoden, welche den entsprechenden Eintrag zurückgeben, nämlich getSourceRepository() und getSourceStamp(). Beide rufen die eben erstellte Methode getApplicationValue() auf, die Parameter sind entsprechend “App” und “SourceRepository” sowie “App” und “SourceStamp”.

getApplicationValue : function(section, key) {
var applicationFile = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties).get('CurProcD', Ci.nsIFile);
applicationFile.append('application.ini');
},

getSourceRepository : function() {
return this.getApplicationValue('App', 'SourceRepository');
},

getSourceStamp : function() {
return this.getApplicationValue('App', 'SourceStamp');
}

Damit das so funktioniert, muss die Methode getApplicationValue() auch noch etwas zurückgeben, denn momentan passiert mit unseren Parametern ja überhaupt nichts. Firefox bietet zu diesem Zweck einen INI-Parser, welcher im Interface nsIINIParserFactory implementiert ist. Die createINIParser()-Methode bekommt dabei die zuvor ausgewählte Datei als Parameter auf den Weg. Daher ergänzen wir diese Methode noch um Folgendes:

return Cm.getClassObjectByContractID('@mozilla.org/xpcom/ini-parser-factory;1', Ci.nsIINIParserFactory)
.createINIParser(applicationFile).getString(section, key);

Da Cm hier wieder ein Symbol ist, nämlich für Components.manager, müssen wir unsere erste Programmzeile noch anpassen:

const {Cc, Ci, Cm} = require('chrome');

Nun wollen wir natürlich wissen, ob wir bis hierhin alles richtig gemacht haben und nutzen dafür unsere init()-Methode, indem wir den Link zum Pushlog in der Konsole ausgeben. Dazu muss die Struktur des Links bekannt sein:

/pushloghtml?fromchange=&tochange=
Beispiel: http://hg.mozilla.org/releases/mozilla-release/pushloghtml?fromchange=c5...

Okay, das SourceRepository ist klar, genauso auch der SourceStamp. Aber wir benötigen noch den PrevSourceStamp, sprich den Changeset des vorherigen Builds. Diesen kennt Firefox nicht. Das heißt, uns bleibt nichts anderes übrig, als diesen abzuspeichern und beim Firefox-Update zu aktualisieren. Aber wir wollen ja nun wissen, ob wir bis hierhin richtig gearbeitet haben. Also verwenden wir an dieser Stelle einfach zweimal den SourceStamp:

init : function() {
console.error(this.getSourceRepository() + '/pushloghtml?fromchange=' + this.getSourceStamp() + '&tochange=' + this.getSourceStamp());
},

Wenn die Fehlerkonsole keinen Fehler wirft und wir bis hierhin also alles richtig gemacht haben, geht es nun an das Speichern der Changesets, das machen wir über about:config-Schalter, wie wir es bereits im ersten Tutorial gelernt haben. Dazu folgende Überlegung: Wir speichern das Changeset des aktuellen Builds in einer Variablen ab. In einer zweiten Variable lesen wir den Inhalt unserer Einstellung für das aktuelle Changeset aus, wir nennen den Schalter in diesem Beispiel extensions.pushlog.changeset.current. Existiert diese Einstellung noch nicht (vor dem ersten Programmupdate nach Installation der Erweiterung) setzen wir hier den aktuellen Wert ein. Anschließend überprüfen wir, ob dieser Schalter noch nicht existiert oder ob beide Variablen unterschiedlich sind. Und wenn eine dieser Bedingungen erfüllt ist, wird extensions.pushlog.changeset.current auf das aktuelle Changeset gesetzt und ein weiterer Schalter, nämlich extensions.pushlog.changeset.previous auf den Wert, welchen wir vorher aus extensions.pushlog.changeset.current ausgelesen und in einer zweiten Variable gespeichert hatten. Das Ganze machen wir in unserer init()-Methode, welche nach Start von Firefox aufgerufen wird. Auf diese Weise stellen wir sicher, dass nach einem Update von Firefox der alte SourceStamp als vorheriger gespeichert wird und der des aktuellen Builds als aktueller SourceStamp gesehen wird. Zur Erinnerung: Der zweite Parameter der prefs.get()-Methode steht für einen Standardwert, welcher angenommen wird, wenn der Schalter nicht existiert.

var changeset = this.getSourceStamp();
var current = prefs.get('extensions.pushlog.changeset.current', this.getSourceStamp());

if(!prefs.has('extensions.pushlog.changeset.current') || changeset != current) {
prefs.set('extensions.pushlog.changeset.previous', current);
prefs.set('extensions.pushlog.changeset.current', changeset);
}

Damit das aber überhaupt funktioniert, müssen wir das preferences-service-Modul zu Beginn erst noch einbinden:

const prefs = require('preferences-service');

Nun können wir auch die getPrevSourceStamp()-Methode implementieren. Statt wie bei der getSourceStamp()-Methode etwas aus der ini-Datei auszulesen, lesen wir hir nun die Einstellung aus:

getPrevSourceStamp : function() {
return prefs.get('extensions.pushlog.changeset.previous', this.getSourceStamp());
},

Die testweise console.error()-Zeile aus der init()-Methode kann wieder entfernt werden, stattdessen packen wir den Inhalt dessen in eine eigene Methode getPushlogUrl() und geben dies zurück, das erste getSourceStamp() ersetzen wir dabei mit getPrevSourceStamp():

getPushlogUrl : function() {
return this.getSourceRepository() + '/pushloghtml?fromchange=' + this.getPrevSourceStamp() + '&tochange=' + this.getSourceStamp();
}

Jetzt sind wir fast fertig! Wir wollen die URL zum Pushlog ja sinnvoll verwenden. Deswegen wollen wir ein Icon in der Addon-Leiste platzieren, bei Klick auf dieses soll sich der Pushlog in einem neuen Tab öffnen, welcher im Hintergrund geladen wird. Das ist exakt das, was wir bereits aus dem ersten Tutorial kennen, daher nur noch in aller Kürze: Wir benötigen die Widget-API, die Tabs-API sowie die Self-API, da wir ein Icon mitliefern werden:

const self = require('self');
const tabs = require('tabs');
const widget = require('widget');

In unserer main()-Funktion erstellen wir nun das Widget nach der init()-Methode:

widget.Widget({
id : 'pushlog',
label : 'Pushlog',
contentURL : self.data.url('pushlog.png'),
onClick : function() {
tabs.open({
url : pushlog.getPushlogUrl(),
inBackground : true
});
}
});

Nun noch im Add-on Builder per Klick auf das [+] unter Data eine Grafik hochladen, welche entsprechend dem self.data.url() benannt ist und die Erweiterung kann gepackt werden.

Hier noch einmal der komplette Code der Demo-Erweiterung:

const {Cc, Ci, Cm} = require('chrome');
const prefs = require('preferences-service');
const self = require('self');
const tabs = require('tabs');
const widget = require('widget');

var pushlog = {
init : function() {
var changeset = this.getSourceStamp();
var current = prefs.get('extensions.pushlog.changeset.current', this.getSourceStamp());

if(!prefs.has('extensions.pushlog.changeset.current') || changeset != current) {
prefs.set('extensions.pushlog.changeset.previous', current);
prefs.set('extensions.pushlog.changeset.current', changeset);
}
},

getApplicationValue : function(section, key) {
var applicationFile = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties).get('CurProcD', Ci.nsIFile);
applicationFile.append('application.ini');

return Cm.getClassObjectByContractID('@mozilla.org/xpcom/ini-parser-factory;1', Ci.nsIINIParserFactory)
.createINIParser(applicationFile).getString(section, key);
},

getSourceRepository : function() {
return this.getApplicationValue('App', 'SourceRepository');
},

getPrevSourceStamp : function() {
return prefs.get('extensions.pushlog.changeset.previous', this.getSourceStamp());
},

getSourceStamp : function() {
return this.getApplicationValue('App', 'SourceStamp');
},

getPushlogUrl : function() {
return this.getSourceRepository() + '/pushloghtml?fromchange=' + this.getPrevSourceStamp() + '&tochange=' + this.getSourceStamp();
}
}

exports.main = function() {
pushlog.init();

widget.Widget({
id : 'pushlog',
label : 'Pushlog',
contentURL : self.data.url('pushlog.png'),
onClick : function() {
tabs.open({
url : pushlog.getPushlogUrl(),
inBackground : true
});
}
});
};

Achtung: Dies ist eine vereinfachte Version meiner Current Pushlog-Erweiterung. Die originale Erweiterung unterstützt verschiedene Release-Kanäle und Möglichkeiten, den Pushlog zu öffnen. Außerdem erscheint, wenn noch kein Firefox-Update durchgeführt worden ist, eine Meldung in einem Panel. Im Rahmen dieses Tutorials habe ich den Funktionsumfang bewusst auf das Wesentliche reduziert.

Firefox Add-on: Current Pushlog 1.0

Aus Eigenbedarf habe ich mit Current Pushlog eine Erweiterung für Firefox geschrieben, welche die Add-on Leiste von Firefox um ein Icon erweitert, über welches man schnell zum aktuellen sogenannten Pushlog zwischen zwei Firefox-Versionen gelagt. Dieser beinhaltet alle Änderungen von Firefox von der letzten zur aktuellen Firefox-Version.

Dabei werden folgende Release-Kanäle unterstützt: mozilla-central, mozilla-aurora, mozilla-beta, mozilla-release, mozilla-esr10 & ux.

Standardmäßig wird der Pushlog bei Klick auf das Icon in einem neuen Tab im Hintergrund geladen. Dieses Verhalten kann über about:config verändert werden:

extensions.pushlog.settings.mode => 1: Pushlog wird im gerade aktiven Tab geöffnet
extensions.pushlog.settings.mode => 2: Pushlog wird in neuem Tab im Hintergrund geöffnet (Standard)
extensions.pushlog.settings.mode => 3: Pushlog wird in neuem Tab geöffnet und in den Vordergrund geholt

Es muss nach Installation der Erweiterung mindestens ein Firefox-Update durchgeführt werden, damit ein erster Pushlog zur Verfügung steht. Die Erweiterung selbst erfordert zur Installation keinen Neustart des Browsers, da sie mit dem Add-on SDK erstellt wurde.

Update 21.02.2012: Mit Version 1.1 erhält Current Pushlog auch Unterstützung für die ESR-Version von Firefox. Außerdem erscheint nun ein Hinweis anstelle eines leeren Pushlogs, wenn noch kein Pushlog vorhanden ist.

Viele neue Bilder zum kommenden Firefox-Design “Australis”

Mozilla plant für die zweite Jahreshälfte 2012 ein neues Design für Firefox. In den letzten Wochen hatte ich bereits einiges zum kommenden “Australis”-Design gezeigt, nun gibt es wieder viele neue Bilder dazu. Die Bilder zeigen alle Windows mit Aero-Oberfläche, im Folgenden eine Auswahl von 18 der neuen Bilder.

Das erste Bild zeigt den neuen Info-Dialog von Firefox. Die Versionsnummer rückt hier in die rechte obere Ecke mit vergleichsweise kleiner Schrift, womit noch einmal deutlich wird, dass Mozilla die Versionsangabe nicht so sehr in den Vordergrund stellt wie es so mancher Anwender gerne tut. ;)

Hier sehen wir die App Tabs und die Tableiste mit vielen geöffneten Tabs:

Als Nächstes folgen die Sidebar mit dem Verlauf der besuchten Seiten sowie die Suchleiste, welche sich in Zukunft oben und nicht länger unten befinden wird. Das dritte der folgenden Bilder zeigt Firefox mit Lesezeichenleiste sowie weiterer Toolbar.

Und so soll die “neue klassische” Symbolleiste aussehen:

Die nächsten beiden Bilder zeigen die jeweilige Meldung, wenn der Server einer Seite nicht gefunden werden kann oder eine Seite aus Sicherheitsgründen blockiert wurde. Auffällig hierbei: Die Adressleiste ist in diesem Fall schwarz gefärbt.

Dieses Bild zeigt den neuen Add-on Manager. Dort zu sehen ist auch eine sogenannte Breadcrumbs-Navigation, welche bereits andeutet, dass die Einstellungen in Zukunft ebenfalls in Tabs sein werden und nicht mehr in einem eigenen Dialog.

Die nächsten sechs Bilder zeigen das neue Design der sogenannten “Doorhanger Notifications”, konkret das Herunterladen eines neuen Add-ons, die Downloads (was den bisherigen Download-Dialog ersetzen wird), das Bearbeiten eines Lesezeichens, das Speichern eines Passworts, das Freigeben des Ortes (Geolocation) sowie die Identitätsanzeige einer Webseite.


Die letzten beiden Bilder zeigen den Lesezeichen-Manager, welcher ebenfalls in Zukunft in einem Tab anstelle eines eigenen Fensters Platz finden wird, sowie das Panorama-Feature.

Update 25.02.2012: Ein neuer Entwurf zeigt, wie das Surfen im Privaten Modus in Zukunft aussehen soll. In diesem Modus legt Firefox keine Chronik an. Anstelle eines andersfarbigen Menüs soll Firefox in diesem Modus komplett schwarz werden.

Firefox 10.0.2 behebt Sicherheitslücke

In aller Kürze: Wenige Tage nach Veröffentlichung von Firefox 10.0 und Firefox 10.0.1 legt Mozilla noch einmal nach und veröffentlicht die Version 10.0.2 sowohl für die reguläre Version des Browsers als auch für die ESR-Version. Mit dem Update wird eine Sicherheitslücke geschlossen.

Download Mozilla Firefox 10.0.2

Firefox 10.0.1 behebt Problem mit Java (und Adressleiste)

Mozilla hat ein erstes Update für Firefox 10.0 und Firefox ESR 10.0 veröffentlicht. Die neue Version 10.0.1 behebt ein Problem im Zusammenhang mit Java-Applets, bei dem es dazu kommen konnte, dass keine Textfelder mehr benutzbar waren, dazu gehörten auch die Adressleiste und das Suchfeld des Browsers.

Download Mozilla Firefox 10.0.1

Neue Mockups zeigen Firefox ohne orangefarbenen Menübutton

Firefox bekommt einen frischen Anstrich. Bereits vor einem halben Jahr habe ich erste Entwürfe des neuen Themas, welches auf den Namen Australis hört, vorgestellt. Einen Monat später gab es dann aktualisierte Bilder. Nun sind neue Mockups von Stephen Horlander aufgetaucht.

Es handelt sich dabei um aktualisierte Entwürfe für Windows (Aero), Mac OS X sowie zweimal Linux (Unity, Gnome3). Was bei dem Windows-Screen sofort ins Auge fällt, ist das Fehlen des orangefarbenen Menübuttons in der linken oberen Ecke des Browserfensters. Auch die Linux-Entwürfe lassen das Menü an gewohnter Stelle vermissen. Bei einem zweiten Blick fällt dafür ein neuer Menübutton auf, der sich in der Haupttoolbar von Firefox befindet, wie es bereits die alten Entwürfe für Mac OS X gezeigt hatten. Man kann sagen, dass die generelle Designlinie nun auf allen drei Desktop-Systemen vereinheitlicht wurde.

Nachtrag 10.02.2012: Auch zu den Entwickler-Werkzeugen von Firefox gibt es ein aktualisiertes Mockup:

Adobes Flashplayer für Firefox läuft künftig in einer Sandbox

Adobe hat eine erste Testversion (11.2.300.130) vom Adobe Flashplayer zum Download freigegeben, in welcher der Flash Player in Firefox im sogenannten Protected Mode läuft.

Das Flashplayer-Plugin ist leider dafür bekannt, immer wieder viele ausnutzbare Sicherheitslücken zu besitzen. Im Protected Mode läuft der Flashplayer für Firefox in einem seperaten Prozess mit deutlich weniger Rechten und kann nur über einen Broker mit dem System kommunizieren, was die ausgehende Gefahr von Schwachstellen im Flashplayer reduziert. In Google Chrome funktioniert ein solches Sandbox-Konzept bereits seit längerem gut – für den Internet Explorer gibt es bislang noch keine entsprechende Version.

Voraussetzung hierfür ist Windows Vista/7 und Firefox in Version 4.0 oder höher. Außerdem steht lediglich eine 32-Bit-Version zur Verfügung. (via)

2. Mozilla-Stammtisch im Ruhrgebiet

Der zweite Mozilla-Stammtisch im Ruhrgebiet findet Dienstag, am 7.2.2012, um 19 Uhr, im Essener unperfekthaus statt. Eingeladen ist jeder, der sich über Mozilla, die Idee, die Organization und die Produkte unterhalten möchte. Es ist keine feste Agenda vorgesehen. Anwender, Helfer, Übersetzer und Entwickler sind herzlich willkommen.

Neben Diskussionen über die aktuellen Produkte und die Situation der deutschen Community, wird auch die (Technik-)Welt im allgemeinen Thema sein. Außerdem helfen wir auch gern bei Problemen mit den verschienen Mozilla-Produkten, wie Firefox, Thunderbird oder Seamonkey.

Die Daten im Überblick:

Zeit: Dienstag, 7.2.2012, 19 Uhr (jeder erste Dienstag im Monat)

Ort: unperfekthaus. Friedrich-Ebert-Str. 18, 45127 Essen-City

Ihr erkennt uns am Firefox-Maskottchen auf dem Tisch.

Inhalt abgleichen