Technologie
Jeżeli tworzyłeś już wcześniej strony oparte na DHTML prawdopodobnie bardzo szybko
zrozumiesz ideę tworzenia rozszerzeń do FireFox. Wiedza którą posiadasz z dziedziny znanych już
wcześniej technologii wymaga jedynie przestawienia się na inny "framework", jakim jest
środowisko FireFox. Rozszerzenia FireFox opierają się na 4 poniższych technologiach i warstwach:
•
•
•
JavaScript
•
CSS
XPCOM (Cross Platform Component Object Model)
XPCOM to crossplatformowy model komponentów podobny do rozwiązania Microsoft COM.
Model ten może być używany i implementowany w wielu językach, w tym JavaScript - języku
wykorzystywanym przez dodatki FireFox. Ogólnie XPCOM oferuje zestaw podstawowych
komponentów i klas m.in. do obsługi plików, zarządzania pamięcią, zarządzania wątkami, ... W
przypadku XPCOM FireFox większość komponentów jest częścią silnika Gecko.
Innymi słowy XPCOM to warstwa umożliwiająca dostęp do operacji, które nie są możliwe
bezpośrednio z pozycji JavaScript. Przykładem jest chociażby dostęp do plików. Łącznikiem
pomiędzy JavaScript a XPCOM jest interfejs
XUL
XUL jest językiem opisu interfejsu użytkownika opartym na język XML. XUL posiada zbiór
podstawowych kontrolek i widgetów używanych w aplikacjach (przyciski, pulldowny, menu, ...).
Najprostszym sposobem, aby przekonać się czym dokładnie jest XUL będzie wpisanie w polu
adresu przeglądarki:
chrome://browser/content/browser.xul
Proszę bardzo, mamy przeglądarkę w przeglądarce! Teraz wystarczy otworzyć Firebug i skorzystać
z funkcji inspect. Mam nadzieję, że jest to dość obrazowe wyjaśnienie roli jaką pełni XUL w
FireFox :-)
JavaScript
Javascript jest warstwą logiki rozszerzeń i łącznikiem z XPCOM i XUL (dzięki DOM).
CSS
Warstwa opisu prezentacji XUL.
Środowisko developerskie
Aby móc efektywnie pisać dodatki do FF musimy przygotować odpowiednie środowisko
developerskie. W pierwszej kolejności należy stworzyć nowy profil FireFoxa. W tym celu
tworzymy nowy skrót:
firefox.exe -no-remote -P dev
Po jego uruchomieniu powinno pojawić się okno zarządzania profilami przeglądarki. Założymy tam
nowy profil 'dev'.
Po uruchomieniu nowego profilu wpisujemy w oknie adesu about:config i zmieniamy
konfigurację następujących zmiennych, które będą przydatne przy testowaniu pisanych rozszerzeń:
nglayout.debug.disable_xul_cache - true
browser.dom.window.dump.enabled - true
javascript.options.showInConsole - true
javascript.options.strict - true
Jeżeli któraś z powyższych zmiennych nie istnieje, należy ją dodać naciskając prawy przycisk
myszy i wybierając Dodaj ustawienie typu -> Wartość logiczna (Boolean)
Warto również zainstalować bardzo pomocne dodatki:
oraz
. Chromebug to
narzędzie napisane na bazie Firebug pracujące na warstwie chrome XUL, czyli warstwie niższej od
tej, na której pracuje Firebug. Po instalacji dodatku Chromebug należy uruchomić FireFox z
dodatkowym parametrem:
firefox.exe -no-remote -P dev -chromebug
Struktura plików i katalogów rozszerzeń FireFox
Po dodaniu i skonfigurowaniu nowego profilu w FireFox możemy już rozpocząć pracę nad
własnym dodatkiem. Nowo dodany profil będzie domyślnie dostępny w podobnej ścieżce:
C:\Documents and Settings\nazwa_uzytkownika\Dane
aplikacji\Mozilla\Firefox\Profiles\mhawpb4j.dev\
Tam też utworzymy nowy katalog, w którym znajdzie się nasz dodatek:
extensions\moj_dodatek@dowolna_najlepiej_wlasna_domena.com
Przyjęło się, że nazwa katalogu (również ID dodatku) ma postać adresu e-mail. Jest to jednak rzecz
umowna, możemy go nazwać zupełnie dowolnie.
Wewnątrz nowo utworzonego katalogu należy stworzyć odpowiednią strukturę plików oraz
podkatalogów:
install.rdf
chrome.manifest
content/
locale/
skin/
Pliki oraz ich strukturę opiszę poniżej. Warto zwrócić uwagę na trzy powyższe katalogi tzw.
pakietów:
•
content - Zawiera pliki XUL oraz JavaScript.
•
locale - Tutaj znajdują się pliki językowe interfejsu.
•
skin - Pliki związane z wizualizacją GUI, takie jak pliki CSS i obrazki.
Budujemy dodatek
W tutorialu tym spróbujemy zbudować dodatek, który będzie zapisywał w pliku tekstowym
wyszukiwane w Google wyrażenia oraz ich datę. Nazwiemy go GoogleQueryLog. Dodatek bardzo
prosty, ale może przy okazji komuś się przyda :-).
Do zrobienia mamy następujące elementy:
•
Warstwa logiki (JavaScript) odpowiedzialna za zbieranie informacji o wyszukiwanych
wyrażeniach. Będzie ona realizowała:
•
przechwycenie zdarzenia zmiany adresu w polu adresu przeglądarki
•
sprawdzenie czy adres URL odpowiada wyrażeniu regularnemu odpowiadającemu
URI wyszukiwania w google
•
jeżeli adres będzie pasował do wzorca zostanie z niego pobrane wyszukiwane
wyrażenie
•
wyrażenie zostanie zapisane w pliku tekstowym wraz z datą
•
Nowa pozycja w menu przeglądarki Google Query Log -> Zobacz logi
•
Okno wyświetlające logi z pliku tekstowego
W katalogu extension profilu 'dev' FireFox zakładamy katalog: googlequerylog@moje.testy.
Budujemy w nich opisaną powyżej strukturę katalogów i plików.
chrome.manifest
Plik chrome.manifest rejestruje pakiet dodatku chrome w FireFox. Zawartość pliku
chrome.manifest będzie następująca:
content googlequerylog content/
skin googlequerylog classic/1.0 skin/classic/
locale googlequerylog en-US locale/en-US/
locale googlequerylog pl-PL locale/pl-PL/
overlay chrome://browser/content/browser.xul
chrome://googlequerylog/content/overlay.xul
Pierwsza linia rejestruje pakiet typu content. googlequerylog to nazwa pakietu, content/
to relatywna ścieżka do folderu gdzie będą znajdować się pliki źródłowe. W sposób analogiczny
definiowane są pozostałe pakiety: skin i locale. Nasz dodatek będzie dostępny w dwóch
wersjach językowych: angielskiej(US) i polskiej. Dla pozostałych wersji językowych FF
domyślnym językiem interfejsu dodatku będzie en-US.
Ostatnia linia określa tzn overlay. Overlay to technika pozwalająca na nałożenie na warstwę
dokumentu XUL kolejnej warstwy. Wyżej podałem przykład wpisania adresu
chrome://browser/content/browser.xul w polu adresu przeglądarki. browser.xul
to dokument XUL okna przeglądarki FireFox. Na tą warstwę nałożymy własny dokument XUL.
Potrzebne będzie nam to do tego, aby osadzić dodatkowo menu w przeglądarce opisane w
założeniach naszego przykładowego dodatku.
install.rdf
Jest to dokument XML zawierający dane dotyczące dodatku, które są wymagane do jego instalacji
w FireFox. Dokument ten będzie miał postać:
01.<?xml version="1.0"?>
02.<RDF xmlns="
http://www.w3.org/1999/02/22-rdf-syntax-ns
http://www.mozilla.org/2004/em-rdf
03.<Description about="urn:mozilla:install-manifest">
04.<!-- Unikalny identyfikator dodatku -->
05.<em:id>googlequerylog@moje.testy</em:id>
06.<!-- Informacje, że jest to dodatek do FF -->
07.<em:type>2</em:type>
08.<!-- Nazwa dodatku która będzie wyświetlana w menadżerze
dodatków FF -->
09.<em:name>Google Query Log</em:name>
10.<!-- Wersja dodatku -->
11.<em:version>0.0.1</em:version>
12.<!-- Opis który będzie widoczny w menadżerze dodatków FF -->
13.<em:description></em:description>
14.<!-- Autor -->
15.<em:creator>mturek</em:creator>
16.<!-- Strona dodatku -->
17.<em:homepageURL>
18.<em:targetApplication>
19.<Description>
20.<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
21.<!-- Minimalna wersja FF którą obsługuje dodatek -->
22.<em:minVersion>3.0</em:minVersion>
23.<!-- Maksymalna wersja FF którą obsługuje dodatek -->
24.<em:maxVersion>3.6.*</em:maxVersion>
25.</Description>
26.</em:targetApplication>
27.</Description>
28.</RDF>
content/overlay.xul
Plik overlay.xul umieścimy w katalogu content, czyli wskazanym w pliku
chrome.manifest:
01.<?xml version="1.0"?>
02.<!DOCTYPE window SYSTEM
"
chrome://googlequerylog/locale/qooglequerylog.dtd
03.<overlay id="googlequerylogOverlay"
xmlns="
http://www.mozilla.org/keymaster/gatekeeper/there.is.only.x
04.<script type="application/javascript"
src="
chrome://googlequerylog/content/service.js
05.<menubar id="main-menubar">
06.<menu id="googlequerylog-menu" label="Google Query Log"
insertafter="helpMenu">
07.<menupopup id="sitehoover-menupopup">
08.<menuitem id="sitehoover-menu-showlog"
label="&overlay.menuShowLogs;"
oncommand="Service.dialogLogOpen()" />
09.</menupopup>
10.</menu>
11.</menubar>
12.</overlay>
Teraz pora uruchomić developerski profil FireFoxa.
Powinniśmy w menu zobaczyć nową pozycję:
Wytłumaczę w jaki sposób na podstawie powyższego pliku XUL została dodana nowa pozycja
menu. Używając Chromebug skorzystaj z funkcji inspect i najedź myszą na górną belkę menu. W
DOM zauważysz, że w głównym dokumenci XUL przeglądarki
chrome://browser/content/browser.xul jest obiekt o ID main-menubar. Ten sam ID
jest użyty w naszym dokumencie XUL. FireFox uruchamiając nasz dodatek połączył oba
dokumenty wiedząc do którego obiektu ma się "przykleić". W kolejnym tagu naszego XUL (menu)
jest atrybut insertafter który ma wartość helpMenu. Jest ID elementu menu "Pomoc" ...
Prawda, że proste? :-)
W naszym dokumencie XUL dołączony jest plik
chrome://googlequerylog/content/service.js, czyli nasza lokalna biblioteka
skryptu JS, która za chwilę stworzymy. Będzie ona zawierała metody potrzebne do realizacji
naszego zadania.
content/service.js
Pora zabrać się za logikę naszej aplikacji, czyli skrypt JavaScript obsługujący nasz dodatek. Plik
umieścimy również w katalogu content (analogiczna ścieżka, jak ta określona w stworzonym
dokumencie XUL).
001.try {
002.netscape.security.PrivilegeManager.enablePrivilege("Universa
lXPConnect");
003.} catch (e) {
004.alert("Dostęp do pliku niedozwolony (FF).");
005.}
006.
007.window.addEventListener('load', function () {
008.gBrowser.addEventListener('DOMContentLoaded', function ()
{
009.Service.parseUrl(gBrowser.currentURI.spec);
010.}, false)
011.}, false);
012.
013.
014.var Service = {
015.extensionId: 'googlequerylog@moje.testy',
016.logFilename: 'qooglequerylog.log',
017.query: '',
018.logFile: null,
019.
020.dialogLogOpen: function(){
chrome://googlequerylog/content/showlo
','','chrome,centerscreen,modal');
022.},
023.
024.dialogLogInit: function(){
025.var logContent = Service.getQueryLog();
026.document.getElementById('googlequerylog-textbox').value =
logContent;
027.},
028.
029.parseUrl: function(_url){
030.reg = new RegExp(/^http:\/\/www.google.(pl|
com)\/.*(&|\?)q=(.*?)&/);
031.reg_dest = reg.exec(_url);
032.if (reg_dest != null){
033.var query = decodeURIComponent(reg_dest[1].replace(/\
+/g, '%20'));
034.if (query != this.query){
035.this.saveQueryLog(query);
036.}
037.}
038.this.query = query;
039.},
040.
041.fileInit: function(){
042.var extension =
Components.classes["@mozilla.org/extensions/manager;1"]
043..getService(Components.interfac
es.nsIExtensionManager)
044..getInstallLocation(this.extens
ionId)
045..getItemLocation(this.extension
Id);
046.var filePath = extension.path + "\\" + this.logFilename;;
047.this.logFile =
Components.classes["@mozilla.org/file/local;1"]
048..createInstance(Components.interfac
es.nsILocalFile);
049.this.logFile.initWithPath(filePath);
050.if(this.logFile.exists() == false){
051.this.logFile.create( Components.interfaces.nsIFile.NOR
MAL_FILE_TYPE, 420);
052.}
053.},
054.
055.saveQueryLog: function(_query){
056.var now = new Date();
057.var content = this.getDate() + '|' + _query + '\r\n';
058.this.fileInit();
059.var outputStream =
Components.classes["@mozilla.org/network/file-output-
stream;1"]
060..createInstance(Components.
interfaces.nsIFileOutputStream)
;
061.var convertOutputStream =
Components.classes["@mozilla.org/intl/converter-output-
stream;1"]
062..createInstance(Component
s.interfaces.nsIConverterOutp
utStream);
063.outputStream.init(this.logFile, 0x02 | 0x08 | 0x10, 0666,
0);
064.
065.// konwertujemy do UTF-8 i zapisujemy plik
066.convertOutputStream.init(outputStream, "UTF-8", 0, 0);
067.convertOutputStream.writeString(content);
068.convertOutputStream.close();
069.},
070.
071.getQueryLog: function(){
072.this.fileInit();
073.var inputStream =
Components.classes["@mozilla.org/network/file-input-
stream;1"]
074..createInstance(Components.in
terfaces.nsIFileInputStream);
075.var convertInputStream =
Components.classes["@mozilla.org/intl/converter-input-
stream;1"]
076..createInstance(Componen
ts.interfaces.nsIConverterIn
putStream);
077.inputStream.init(this.logFile, 0x01, 00004, null);
078.convertInputStream.init(inputStream, 'UTF-8', 1024,
0xFFFD);
079.convertInputStream.QueryInterface(Components.interfaces.n
sIUnicharLineInputStream);
080.var outputArray = new Array();
081.if (convertInputStream instanceof
Components.interfaces.nsIUnicharLineInputStream) {
082.var line = {};
083.var cont;
084.do {
085.cont = convertInputStream.readLine(line);
086.outputArray.push(line.value.split("|"));
087.} while (cont);
088.}
089.convertInputStream.close();
090.outputArray.reverse();
091.var output = '';
092.for (var i = 0; i < outputArray.length; i++ ){
093.if (outputArray[i][1]){
094.output += outputArray[i][0] + ' - ' +
outputArray[i][1] + "\r\n";
095.}
096.}
097.return output;
098.},
099.
100.getDate: function(){
101.var d = new Date();
102.function pad(n){
103.return n<10 ? '0'+n : n
104.}
105.return d.getFullYear()+'-'
106.+ pad(d.getMonth()+1)+'-'
107.+ pad(d.getDate())+' '
108.+ pad(d.getHours())+':'
109.+ pad(d.getMinutes())+':'
110.+ pad(d.getSeconds());
111.}
112.}
Na początku pliku dodajemy obsługę zdarzenia 'load' okna przeglądarki, które będzie
wywoływało funkcję Service.parseUrl(). Argumentem tej funkcji jest adres otwieranej
strony. Przekazywany adres URL będzie porównywany ze wzorcem odpowiadającym adresowi
query wyszukiwania w Google i w przypadku dopasowania wzorca wyszukiwane wyrażenie
zostanie zapisane w pliku. Aby uzyskać dostęp do lokalnego systemu plików użyte zostały
komponenty XPCOM. Za zapis logów odpowiada funkcja Service.saveQueryLog().
content/showlogs.xul
W dokumencie overlay.xul w elemencie menuitem dodana jest obsługa zdarzenia oncommand.
Jest to zdarzenie, które zostanie wywołane po wybraniu myszką pozycji menu "Pokaż logi" - czyli
wywołanie metody Service.dialogLogOpen(). Metoda ta otwiera w nowym oknie
dokument showlogs.xul:
01.<?xml version="1.0"?>
02.<?xml-stylesheet href="
03.<?xml-stylesheet href="
chrome://googlequerylog/skin/style.css
04.<!DOCTYPE window SYSTEM
"
chrome://googlequerylog/locale/qooglequerylog.dtd
05.<window id="googlequerylog-logs-dialog"
xmlns="
http://www.mozilla.org/keymaster/gatekeeper/there.is.only.x
06.title="&logswindow.title;"
07.onload="Service.dialogLogInit();">
08.<script type="application/javascript"
src="
chrome://googlequerylog/content/service.js
09.<box height="400" width="400">
10.<textbox id="googlequerylog-textbox" flex="1"
multiline="true" readonly="true" />
11.</box>
12.<button label="&logswindow.closButton;"
oncommand="window.close();" />
13.</window>
Zwróć uwagę na zdarzenie onload na elemencie window. Fukcja
Service.dialogLogInit() uruchamia sekwencje otwarcia pliku logów z dysku oraz
wyświetlenie ich w nowym oknie.
style.css
W chrome.manifest zarejestrowaliśmy pakiet skin. Jest to pakiet, w którym definiujemy
warstwę prezentacji, która reprezentowana jest arkuszem CSS. W naszym dodatku równie dobrze
mogłoby jej nie być, jednak dodałem ją, aby zaprezentować w jaki sposób jest to realizowane w
dodatkach do FireFox. Z arkusza css korzysta showlogs.xul, gdzie jest on dołączany w prologu
dokumentu:
<?xml-stylesheet href="chrome://googlequerylog/skin/style.css"?>
Plik style.css umieszczony jest w lokalizacji skin/classic/style.css
1.#googlequerylog-textbox {
2.-moz-appearance: none;
3.background:#FFF8D6;
4.}
Styl ten zmienia kolor tła elementu texbox. Mozilla wprowadza wiele dodatkowych styli, które
możemy użyć w ramach rozszerzeń. Opis wszystkich znajduje się
. W przykładzie powyżej
znajduje się jeden z nich: -moz-appearance:none, który wyłącza natywne style textboxa. Bez
użycia tego stylu nie bylibyśmy w stanie zmienić tła tego elementu.
Warto zwrócić uwagę również na dodany arkusz chrome://global/skin/. Jest to arkusz
globalny w którym zdefiniowane są style globalne przeglądarki (np. wizualizacja otwieranego okna,
przycisków, ...).
Wersje językowe
Powróćmy ponownie do pliku chrome.manifest. Zdefiniowaliśmy w nim wpisy odpowiadające
deklaracji wersji językowych dodatku:
locale googlequerylog en-US locale/en-US/
locale googlequerylog pl-PL locale/pl-PL/
W dodatku mamy trzy elementy, które należałoby przetłumaczyć na inne języki. Jest to pozycja w
menu "Pokaż logi" oraz nazwa okna z logami i przycisk "Zamknij". Są one umieszczone w
dokumentach XUL. Dokumenty XUL mają składnie XML, więc skorzystamy z encji, które będą
zwierały tłumaczenia.
Encję dołączamy deklaracją DOCTYPE
<!DOCTYPE window SYSTEM "chrome://googlequerylog/locale/qooglequerylog.dtd">
Zgonie z chrome.manifest definicje encji umieścimy w lokalizacjach /locale/pl-
PL/qooglequerylog.dtd oraz analogicznie dla wersji en-US:
<!ENTITY overlay.menuShowLogs "Pokaż logi">
<!ENTITY logswindow.title "Logi">
<!ENTITY logswindow.closButton "Zamknij">
W dokumencie XUL będziemy się do nich odwoływać za pomocą encji, np:
&overlay.menuShowLogs;
Są one umieszczone w plikach overlay.xul i showlogs.xul.
Można zauważyć, że w deklaracji ścieżki DOCTYPE nie są uwzględniane katalogi językowe, np.
pl-PL. Silnik FireFox automatycznie je uzupełnia w zależności od wersji językowej przeglądarki.
Dobrze, ale w jaki sposób skorzystać z etykiet językowych w JavaScript? Akurat w naszym
przykładzie nie było takiej potrzeby, jednak warto przy okazji o tym wspomnieć.
Przykładowo stworzymy plik locale/pl-PL/qooglequerylog.properties
googlequerylog.label1=etykieta 1
googlequerylog.label2=etykieta 2
Poniżej znajduje się przykładowy kod JavaScript pokazujący w jaki sposób możemy skorzystać z
tak zdefiniowanych etykiet:
1.var _bundle =
gmyextensionBundle.createBundle("
chrome://googlequerylog/locale/go
2.alert(_bundle.GetStringFromName('googlequerylog.label1'));
Testy
Chciałbym wspomnieć o dość ważnej kwestii związanej z testowaniem pisanych dodatków. Wyżej
przedstawiłem konfigurację stworzonego nowego profilu FireFox do celów deweloperskich. W
konfiguracji tej zostało m.in. wyłączone cacheowanie ładowanych dokumentów XUL. Jeżeli
dodatek opiera się na niezależnym dokumencie XUL (jak w naszym przypadku okno z logami) jego
testowanie nie wymaga każdorazowego restartu FireFox. Jeżeli jednak testujemy warstwę overlay,
która jest nałożona na główne okno przeglądarki
(chrome://browser/content/browser.xul) konieczne będzie cykliczne zamykanie i
ponowne otwieranie FF.
Paczka dystrybucyjna - XPI
Ostatnim elementem jest przygotowanie dystrybucji dodatku w formie plików XPI. XPI jest niczym
innym jak archiwum ZIP. Paczka XPI zawiera lekko zmodyfikowaną strukturę katalogów i plików
w stosunku do wersji deweloperskiej:
chrome.manifest
install.rdf
chrome/googlequerylog.jar
.jar to również plik ZIP. Do przygotowania paczki archiwów polecam paker
Katalogi content, locale, skin zarchiwizujemy do formatu ZIP i zmienimy nazwę
stworzonego archiwum na googlequerylog.jar. Umieścimy go w katalogu chrome
(zgodnie z powyższa strukturą paczki XPI).
Następnie zmodyfikujemy plik chrome.manifest, tak aby miał postać:
content googlequerylog jar:chrome/googlequerylog.jar!/content/
skin googlequerylog classic/1.0 jar:chrome/googlequerylog.jar!/skin/classic/
locale googlequerylog en-US jar:chrome/googlequerylog.jar!/locale/en-US/
locale googlequerylog pl-PL jar:chrome/googlequerylog.jar!/locale/pl-PL/
overlay chrome://browser/content/browser.xul
chrome://googlequerylog/content/overlay.xul
Widzimy, że dodaliśmy informacje, że poruszamy się względem archiwum .jar.
Teraz całą strukturę plików i katalogów zarchiwizujemy do ZIP i zmienimy nazwę archiwum na
googlequerylog.xpi.
To wszystko. Na koniec wystarczy przeciągnąć stworzony plik XPI do okna FireFox aby go
zainstalować.
Powyższy dodatek można pobrać
Na koniec
Tutorial ten jest jedynie wprowadzeniem do tematu rozszerzeń Mozilli. Możliwości jakie daje nam
FireFox do tworzenia dodatków są naprawdę duże, co zresztą widać po bogatej
. W wielu przypadkach są to bardzo użyteczne dodatki, bez których często
nie można sobie już wyobrazić korzystania z przeglądarki. Jak widać, aby napisać własny dodatek
wystarczy jedynie dobry pomysł, bo samo jego stworzenie, mam nadzieję, nie wydaje się już takie
trudne. Powodzenia!