O arquivo install.rdf
Na última seção, nós vimos o conteúdo da extensão Hello World. Agora vamos dar uma olhada em seus arquivos e códigos, começando com o arquivo install.rdf. Você pode abri-lo com qualquer editor de texto.
O arquivo está formatado em um "seguimento" especial de XML chamado RDF. RDF costumava ser o mecanismo de armazenamento central para o Firefox, mas agora está sendo substituído por um sistema de banco de dados mais simples. Vamos discutir esses dois sistemas de armazenamento mais adiante no tutorial.
Agora vamos olhar para as partes importantes do arquivo.
<em:id>helloworld@xulschool.com</em:id>
Este é o identificador exclusivo para a extensão. Firefox precisa disso para distinguir a sua extensão de outras extensões, por isso é necessário que você tenha um ID que seja único.
Existem dois padrões aceitos para ID de complementos. Um deles é o formato de e-mail como no exemplo Hello World, que deve ser algo como<nome-do-projeto>@<seudominio>. A outra prática comum é utilizar uma string UUID gerada, que é extremamente improvável que seja duplicada. Sistemas baseados em Unix tem uma ferramenta de linha de comando chamado uuidgen que gera UUIDs. Há também ferramentas com download disponível para todas as plataformas que geram elas. Enquanto o seu id tem alguma exclusividade para ele, não há problema em usar de qualquer forma.
<em:name>XUL School Hello World</em:name>
<em:description>Welcome to XUL School!</em:description>
<em:version>0.1</em:version>
<em:creator>Appcoast</em:creator>
<em:homepageURL>https://developer.mozilla.org/en/XUL_School</em:homepageURL>
Estes são os dados que é exibido antes e depois da extensão instalada, que você pode ver no Gerenciador de Complementos(Add-ons). Existem muitas outras marcas que podem ser adicionadas, para colaboradores e tradutores. A especificação completa do arquivo install.rdf tem todos os detalhes.
Desde que extensões podem ser traduzidas para vários idiomas, muitas vezes é necessário traduzir a descrição da extensão, ou até mesmo o seu nome. A descrição localizada eo nome podem ser adicionados com o seguinte código:
<em:localized> <Description> <em:locale>es-ES</em:locale> <em:name>XUL School Hola Mundo</em:name> <em:description>Bienvenido a XUL School!</em:description> </Description> </em:localized>
O es-ES na string locale indica que é Espanhol(es) com localização na Espanha (ES). Você pode adicionar muitos <em:localized> se você precisar. Para Firefox 2, localizar este arquivo é um pouco mais complicado. Vamos discutir a localização mais adiante nesta seção.
<em:type>2</em:type>
Isto especifica que o complemento(add-on) que está sendo instalado é uma extensão. Você pode ler sobre os diferentes tipos possíveis no install.rdf.
<em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:minVersion>4.0</em:minVersion> <em:maxVersion>10.*</em:maxVersion> </Description> </em:targetApplication>
Este node especifica o aplicativo de destino e versões alvo para a extensão, especificamente Firefox, a partir da versão 4 até a versão 10. O UUID é da ID única Firefox. Outras aplicações Mozilla e baseadas no Mozilla, como o Thunderbird e Seamonkey têm a sua própria. Você pode ter uma extensão que funciona em vários aplicativos e versões. Por exemplo, se você criar uma extensão do Firefox, normalmente levaria pouco esforço para portá-lo para o SeaMonkey, que possui recursos e interface do usuário muito semelhantes.
A versão mínima(minVersion) e a versão máxima(maxVersion) especifica o intervalo da versão em que a extensão pode ser instalada.. Veja mais sobre o "version format". Se o intervalo de aplicação ou a versão não coincidirem, você não terá permissão para instalar a extensão, ou a extensão será instalada em um estado desativado. Os usuários podem desativar verificações de versão através de preferências ou a instalação de add-ons como o Add-on Compatibility Reporter. Começando com o Firefox 11, add-ons serão por padrão compatível e o Firefox, na maioria das vezes ignora a faixa de versão. No Entanto, é sempre recomendável testar seus complementos(add-ons) com cada versão do Firefox.
Qualquer erro ou falta de informação fará com que o processo de instalação falhe, ou a extensão vai ser instalada em um estado desativado.
O arquivo chrome.manifest
Chrome é o conjunto de elementos de interface de usuário da janela do aplicativo que estão fora da área de conteúdo da janela. Barras de ferramentas, barras de menus, barras de progresso e barras de título da janela, são exemplos de elementos que normalmente fazem parte do chrome.
Pego de Chrome Registration.
Em outras palavras, o Chrome é tudo o que você vê no Firefox. Todas as janelas do Firefox pode ser visto como tendo duas partes: (1) o Chrome e (2), possivelmente, uma área de conteúdo, como o que exibe páginas da web em uma aba do Firefox. O Windows, como a janela de downloads são puro Chrome. A maior parte do código para uma extensão reside na pasta chrome, assim como no exemplo Hello World.
Como vimos na estrutura de diretório da extensão descompactada, o Chrome é composto de três partes: content, locale e skin. Eles três são necessários para a maioria das extensões. Se abrirmos o arquivo chrome.manifest (novamente, qualquer editor de texto serve), vemos que as mesmas três seções são mencionadas:
content xulschoolhello content/ skin xulschoolhello classic/1.0 skin/ locale xulschoolhello en-US locale/en-US/
O arquivo chrome.manifest fala ao Firefox onde procurar por arquivos chrome. O texto é espaçado para parecer uma planilha, mas isso não é necessário. O analisador ignora espaços repetidos.
A primeira palavra em uma linha diz ao Firefox o que é que está sendo declarado (conteúdo, skin, locale, ou outros mencionados mais tarde). O segundo é o nome do pacote, que vamos explicar em breve.
Pacotes Skin e locale tem um terceiro valor para especificar o local ou qual skin eles estão usando. Pode haver mais de uma skin e locales escolhidos. O caso mais comum é usar uma skin mundial, a "classic/1.0", e múltiplos locale, um para cada tradução. Finalmente, a localização esta especificada.
Existem algumas opções adicionais que podem ser incluídos nas linhas de um arquivo chrome.manifest. Elas estão documentadas na página Chrome Registration. Notavelmente, podemos especificar configurações para um SO específico. Isto é muito importante porque a aparência do navegador é muito diferente em cada Sistema Operacional. Se nossa extensão precisa ser diferente em sistemas diferentes, nós podemos mudar o arquivo manifest para se parecer com isso:
content xulschoolhello content/ skin xulschoolhello classic/1.0 skin/unix/ skin xulschoolhello classic/1.0 skin/mac/ os=Darwin skin xulschoolhello classic/1.0 skin/win/ os=WinNT locale xulschoolhello en-US locale/en-US/
Desta maneira podemos separar skins para o Windows, Mac OS X, e Linux (além de outros sistemas tipo Unix), cada uma definida em um diretório separado. Como a maioria dos outros sistemas são baseados em Unix, a skin "unix" é a padrão, sem flags.
O Chrome
Como mencionado anteriormente, o chrome é composto de 3 seções: content, locale e skin. O content é a mais importante seção, com a interface de usuário (XUL) e arquivos de script (JS). A seção skin tem os arquivos que definem mais a aparência e visual da UI (usando CSS e imagens, igual páginas da web). Finalmente, a seção locale tem todo texto usado na extensão, em DTD e arquivos de propriedades. Está divisão permite outros desenvolvedores criarem temas para substituir as skins, e criar traduções em diferentes linguagens, tudo isso sem ter que mudar sua extensão ou seu code(código). Isto dá as extensões do Firefox grande flexibilidade.
Arquivos Chrome são accessados através do protocolo chrome. Isto precisa de uma chrome URI parecida com isso:
chrome://nomedopacote/seção/caminho/para/arquivo
Então, para instância, se você quer acessar o arquivo browserOverlay.xul na extensão, a chrome URI pode ser chrome://xulschoolhello/content/browserOverlay.xul
Se você tem muitos arquivos no content e quer organizá-los em subdiretórios, não há nada que você precisa mudar em chrome.manifest, tudo que você precisa é adicionar o caminho certo depois de content na URI.
Arquivos Skin e locale funcionam do mesmo jeito, e você não precisa especificar nome das skins ou nomes dos locale. Então, para acessar o arquivo DTD na extensão Hello World, o caminho chrome é chrome://xulschoolhello/locale/browserOverlay.dtd. O Firefox sabe qual locale procurar.
Este é um experimento interessante. Abra uma nova Aba do Firefox, escreva chrome://mozapps/content/downloads/downloads.xul na sua barra de endereços e pressione ENTER. Surpreso? Você só acabou de abrir a janela de Downloads em uma Aba do Firefox! Você pode acessar qualquer arquivo chrome só escrevendo esta URI na barra de endereços. Isto pode ser conveniente se você quer inspecionar arquivos de script que são parte do Firefox, outras extensões, ou a sua própria. A maioria destes arquivos são abertos como arquivos de texto, com a exceção de arquivos XUL, os quais são executados e mostrados como você normalmente vê em uma janela.
Content
Existe 2 arquivos na pasta content. Vamos ver o arquivo XUL primeiro.
Arquivos XUL são arquivos XML que definem os elementos da interface de usuário no Firefox e Extensões Firefox. XUL foi inspirado em HTML, então você irá ver muita coisa parecida entre as duas. Porém, XUL é também uma melhoria sobre o HTML, tendo aprendido de muitos erros cometidos durante a evolução do HTML. XUL permite á você criar ricas e mais interativas interfaces do que o HTML permitirá, ou pelo menos XUL é mais fácil.
Arquivos XUL usualmente definem uma das duas coisas: janelas ou overlays. O arquivo que você tinha aberto antes, downloads.xul, tem o código que define a janela de Downloads. O Arquivo XUL incluído na extensão Hello World é um overlay. Um overlay amplia uma janela existente, adicionando novos elementos nela ou substituindo alguns elementos dela. A linha que nós pulamos no arquivo chrome.manifest coloca que este arquivo XUL é um overlay para a janela principal do navegador:
overlay chrome://browser/content/browser.xul chrome://xulschoolhello/content/browserOverlay.xul
Com esta linha, O Firefox sabe que isso precisa pegar o conteúdo de browserOverlay.xul e colocar esse overlay na janela principal do navegador, browser.xul. Você pode declarar overlays para qualquer janela ou diálogo no Firefox, mas overlays na janela principal do navegador é de longe o caso mais comum.
Agora vamos ver o conteúdo do nosso arquivo XUL. Nós iremos pular algumas linhas do começo pois se referem a skin e o locale, e vamos ver elas mais tarde.
<overlay id="xulschoolhello-browser-overlay" xmlns="https://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
O elemento raiz no arquivo é um overlay. Outros documentos XUL usam a tag window ou dialog. O elemento tem um id único, que você deve ter na maioria dos elementos no seu XUL. O segundo atributo é o namespace, que é algo que você deve sempre definir em seu elemento raiz do arquivo XUL. Ela diz que esse node e todos os outros nodes filhos são XUL. Você só precisa alterar as declarações de namespace quando você mistura diferentes tipos de conteúdo no mesmo documento, tal como XUL com HTML ou SVG.
<script type="application/x-javascript" src="chrome://xulschoolhello/content/browserOverlay.js" />
Como no HTML, isto incluí um arquivo de script JavaScript. Você pode ter quantos "script elements" precisar em um documento XUL. Nós iremos ver isto no code(código) depois.
Nós pulamos algum code(código) que será visto na seção locale, indo para a parte mais importante do conteúdo:
<menubar id="main-menubar"> <menu id="xulschoolhello-hello-menu" label="&xulschoolhello.hello.label;" accesskey="&xulschoolhello.helloMenu.accesskey;" insertafter="helpMenu"> <menupopup> <menuitem id="xulschoolhello-hello-menu-item" label="&xulschoolhello.hello.label;" accesskey="&xulschoolhello.helloItem.accesskey;" oncommand="XULSchoolChrome.BrowserOverlay.sayHello(event);" /> </menupopup> </menu> </menubar> <vbox id="appmenuSecondaryPane"> <menu id="xulschoolhello-hello-menu-2" label="&xulschoolhello.hello.label;" accesskey="&xulschoolhello.helloMenu.accesskey;" insertafter="appmenu_addons"> <menupopup> <menuitem id="xulschoolhello-hello-menu-item-2" label="&xulschoolhello.hello.label;" accesskey="&xulschoolhello.helloItem.accesskey;" oncommand="XULSchoolChrome.BrowserOverlay.sayHello(event);" /> </menupopup> </menu> </vbox>
Este code(código) adiciona o menu Hello World na janela do navegador.
(Nota: Tradução incompleta temporariamente.)
There are two similar code blocks, because in modern versions of Firefox, particularly on Windows, a single Firefox menu button is presented, with simplified menu options, rather than an extensive menu bar. The second code block covers the common menu button case; the first code block covers all other cases. Check Menu Bar under the Options menu of the menu button to toggle display of the classic menu on Windows and some Linux distributions.
In order to write this code, we needed some knowledge of the XUL code in browser.xul. We needed to know that the id of the right pane in the unified menu is appmenuSecondaryPane. We're adding a menu of our own, and telling Firefox to add it in that pane, right after the Add-ons item. That's the purpose of the attribute:
insertafter="appmenu_addons"
appmenu_addons is the id of the menu element that corresponds to the Add-ons menu item in the main menu. We'll see later how we can find out things like the ids of browser elements, but for now let's look at the elements that compose the Hello World menu.
For the classic menu, we added the Hello World menu right in the "root" of the menu so that it would be very easy for you to spot it, but this is not a recommended practice. Imagine if all extensions added menus to the top menu; having a few extensions would make it look like an airplane dashboard, full of knobs and switches. In the case of the unified menu, things are a little more difficult due to lack of options. If your menu item fits in the Web Developer section, it is recommended that you add it there. Otherwise, the root menu might be your only recourse.
One recommended location for menus in the classic menu vase is under the Tools menu, so the code should really look like this:
<menupopup id="menu_ToolsPopup"> <menu id="xulschoolhello-hello-menu" label="&xulschoolhello.hello.label;" accesskey="&xulschoolhello.helloMenu.accesskey;" insertbefore="devToolsEndSeparator"> <menupopup> <menuitem id="xulschoolhello-hello-menu-item" label="&xulschoolhello.hello.label;" accesskey="&xulschoolhello.helloItem.accesskey;" oncommand="XULSchoolChrome.BrowserOverlay.sayHello(event);" /> </menupopup> </menu> </menupopup>
We're overlaying the menu that is deeper into the XUL tree, but it doesn't matter because all we need is the id of the element we want to overlay. In this case it is the menupopup element that's inside of the Tools menu element. The insertbefore attribute tells Firefox to add the menu at the bottom of the dev tools section, above its end separator. We'll discuss more about menus later on in the tutorial.
Now let's look at the actual code:
oncommand="XULSchoolChrome.BrowserOverlay.sayHello(event);"
This attribute defines an event handler. The command event is the most frequently used in Firefox, since it corresponds to the main action for most UI elements. The value of the attribute is JavaScript code that invokes a function. This function is defined in the JS file that was included with the script tag. The JS function will be called once the user clicks on the menu item in the Hello World menu. All event handlers define a special object named event, which is usually good to pass as an argument to the function. Event handlers are explained in greater depth further ahead.
Now let's look at the JavaScript file and see what's going on when the event is fired.
/** * XULSchoolChrome namespace. */ if ("undefined" == typeof(XULSchoolChrome)) { var XULSchoolChrome = {}; };
The XULSchoolChrome namespace is defined. All objects and variables we define in this JavaScript are global, meaning that scripts in Firefox and other extensions can see them and interact with them. This also means that if we define an object called MenuHandler or some other generic name, it's likely going to conflict with an existing object. What we do here is define a single global object: XULSchoolChrome. Now we know that all of our objects are inside this object, which is unlikely to be duplicated or overwritten by other extensions.
You can read more about the typeof operator. If you're unfamiliar with JavaScript or this particular syntax, initializing an object as {} is the equivalent of initializing it to new Object().
/**
* Controls the browser overlay for the Hello World extension.
*/
XULSchoolChrome.BrowserOverlay = {
Finally, BrowserOverlay is our object. Naming and referencing objects in such a long and verbose manner can feel uncomfortable at first, but it's worth the cost.
sayHello : function(aEvent) { let stringBundle = document.getElementById("xulschoolhello-string-bundle"); let message = stringBundle.getString("xulschoolhello.greeting.label"); window.alert(message); }
And, finally, this is our function declaration. Three lines of code are all we need for it to work. The first line in the body of the function declares a variable that will hold the stringbundle element defined in the overlay. The variable is declared using let, which is similar to var but with more restricted scope. Here you can read more about let declarations.
Just like in regular JS, we can use the DOM (Document Object Model) in order to manipulate the XUL document. First we get a reference of the stringbundle element in the document. This is a special element that allows us to obtain localized strings dynamically, by only providing a "key" that identifies the string. This is what we do on the second line. We call the getString method of the bundle element and get the localized message to be displayed. We then call the window.alert function with the message, just like we would do in an HTML document.
Locale
There are two types of locale files: DTD and properties, and in this example we use them both. DTD is the most efficient way of showing text in XUL, so you should use it whenever possible. It is somewhat inflexible so it can't be used for dynamically generated text, hence the need for an alternate way of getting localized strings.
Looking back at the menu code, you probably noticed some attributes such as this:
label="&xulschoolhello.hello.label;" accesskey="&xulschoolhello.helloItem.accesskey;"
These attributes define the text that you see on the menus, and they are string keys that are defined in our DTD file, browserOverlay.dtd. The DTD file was included in the XUL file with the following code:
<!DOCTYPE overlay SYSTEM "chrome://xulschoolhello/locale/browserOverlay.dtd" >
And in the DTD file you can see the association between keys and localized strings:
<!ENTITY xulschoolhello.hello.label "Hello World!"> <!ENTITY xulschoolhello.helloMenu.accesskey "l"> <!ENTITY xulschoolhello.helloItem.accesskey "H">
Notice that on the XUL file you enclose the string key with & and ; while on the DTD file you only specify the key. You may get weird parsing errors or incorrect localization if you don't get this right.
Access keys are the shortcuts that allow you to quickly navigate a menu using only the keyboard. They are also the only way to navigate a menu for people with accessibility problems, such as partial or total blindness, or physical disabilities that make using a mouse very difficult or impossible. You can easily recognize the access keys on Windows because the letter that corresponds to the access key is underlined, as in the following image:
Most user interface controls have the accesskey attribute, and you should use it. The value of the access key is localized because it should match a letter in the label text. You should also be careful to avoid access key repetition. For example, within a menu or submenu, access keys should not be repeated. In a window you have to be more careful picking access keys because there are usually more controls there. You have to be specially careful when picking access keys on an overlay. In our case, we can't use the letter "H" as an accesskey in the Main menu item, because it would be the same as the access key in the Help menu. Same goes with "W" and the Window menu on Mac OS. So we settled on the letter "l".
DTD strings are resolved and set when the document is being loaded. If you request the label attribute value for the Hello World menu using DOM, you get the localized string, not the string key. You cannot dynamically change an attribute value with a new DTD key, you have to set the new value directly:
let helloItem = document.getElementById("xulschoolhello-hello-menu-item"); // The alert will say "Hello World!" alert(helloItem.getAttribute("label")); // Wrong helloItem.setAttribute("label", "&xulschoolhello.hello2.label;"); // Better helloItem.setAttribute("label", "Alternate message"); // Right! helloItem.setAttribute("label", someStringBundle.getString("xulschoolhello.hello2.label"));
This is the reason DTD strings are not a solution for all localization cases, and the reason we often need to include string bundles in XUL files:
<stringbundleset id="stringbundleset"> <stringbundle id="xulschoolhello-string-bundle" src="chrome://xulschoolhello/locale/browserOverlay.properties" /> </stringbundleset>
The stringbundleset element is just a container for stringbundle elements. There should only be one per document, which is the reason why we overlay the stringbundleset that is in browser.xul, hence the very generic id. We don't include the insertbefore or insertafter attributes because the ordering of string bundles doesn't make a difference. The element is completely invisible. If you don't include any of those ordering attributes in an overlay element, Firefox will just append your element as the last child of the parent element.
All you need for the string bundle is an id (to be able to fetch the element later) and the chrome path to the properties file. And, of course, you need the properties file:
xulshoolhello.greeting.label = Hi! How are you?
The whitespace around the equals sign is ignored. Just like in install.rdf, comments can be added using the # character at the beginning of the line. Empty lines are ignored as well.
You will often want to include dynamic content as part of localized strings, like when you want to inform the user about some stat related to the extension. For example: "Found 5 words matching the search query". Your first idea would probably be to simply concatenate strings, and have one "Found" property and another "words matching..." property. This is not a good idea. It greatly complicates the work of localizers, and grammar rules on different languages may change the ordering of the sentence entirely. For this reason it's better to use parameters in the properties:
xulshoolhello.search.label = Found %S words matching the search query!
Then you use getFormattedString instead of getString in order to get the localized string. Thanks to this we don't need to have multiple properties, and life is easier for translators. You can read more about it on the Text Formatting section of the XUL Tutorial. Also have a look at the Plurals and Localization article, that covers a localization feature in Firefox that allows you to further refine this last example to handle different types of plural forms that are also language-dependent.
Skin
Styling XUL is very similar to styling HTML. We'll look into some of the differences when we cover the XUL Box Model, and other more advanced topics. There isn't much styling you can do to a minimal menu and a very simple alert message, so the Hello World extension only includes an empty CSS file and the compulsory global skin file:
<?xml-stylesheet type="text/css" href="chrome://global/skin/" ?> <?xml-stylesheet type="text/css" href="chrome://xulschoolhello/skin/browserOverlay.css" ?>
The global skin CSS file holds the default styles for all XUL elements and windows. Forgetting to include this file in a XUL window usually leads to interesting and often unwanted results. In our case we don't really need to include it, since we're overlaying the main browser XUL file, and that file already includes this global CSS. At any rate it's better to always include it. This way it's harder to make the mistake of not including it. You can enter the chrome path in the location bar and inspect the file if you're curious.
This covers all of the files in the Hello World extension. Now you should have an idea of the basics involved in extension development, so now we'll jump right in and set up a development environment. But first, a little exercise.
Exercise
Make the following changes to the example extension:
- Edit the welcome message that is displayed in the alert window.
- Move the Hello World menu to the Tools menu, where it belongs.
Repackage the XPI. Issue the following command from within the extension root directory on Linux or Mac OS X:
zip ../xulschoolhello2.xpi *
On Windows, use a ZIP tool to compress all files and subdirectories within the extension root directory. Name the file with extension .xpi
Re-install the XPI. You can just drag the XPI file to the browser and it will be installed locally.
Test it and verify your changes worked. If you run into problems at installation, it's likely that you didn't reproduce the XPI structure correctly, maybe adding unnecessary folders.
.XPI
. Do not zip the containing folder, just its contents. The content
folder, chrome.manifest
, install.rdf
, and other files and directories should be at the root level of your archive. If you zip the containing folder, your extension will not load.Note that the Tools menu is hidden by default on Firefox 4 and above, on Windows and some Linux distributions. Check Menu Bar under the Options menu of the Firefox menu button to enable it.
Once you're done, you can look at this reference solution: Hello World 2.
This tutorial was kindly donated to Mozilla by Appcoast.