Com o SDK você não precisa manter tudo em um único arquivo "main.js". Você pode separar seu código em módulos separados com interfaces claramente definidas entre eles. Você então importa e usa estes módulos de outras partes de seu add-on usando a declaração require()
, da mesma forma que você importa os módulos core do SDK como page-mod
or panel
.
Muitas vezes faz sentido estruturar um add-on muito grande ou complexo como uma coleção de módulos. Isso torna o desenho do add-on mais fácil de entender e fornece algum encapsulamento em que cada módulo exportará somente o que ele escolheu, então você pode mudar o módulo internamente sem quebrar seu usuário.
Uma vez que você fez isso, você pode empacotar os módulos e distribui-los independentemente de seu add-on, tornando-os disponíveis para outros desenvolvedores de add-on e efetivamente extendendo o SDK.
Neste tutorial faremos exatamente isso com o módulo que calcula hashes de arquivo.
Um add-on de hashing
Uma função hash leva uma string de qualquer tamanho de bytes, e produz uma string curta e de tamanho fixo de bytes como saída. É um modo útil para criar um "fingerprint" que pode ser usado para identificar um arquivo. MD5 é uma função hash comumente usada: embora não seja considerada segura, ela trabalha bem desconsiderando o contexto da segurança.
Aqui nós escreveremos um add-on que deixa o usuário escolher uma arquivo no disco e calcula seu hash. Para ambas operações nós usaremos as interfaces XPCOM.
File picker
Para deixar o usuário selecionar um arquivo nós usaremos o nsIFilePicker. A documentação para esta interface inclui um exemplo que nós podemos adaptar como este:
var {Cc, Ci} = require("chrome"); function promptForFile() { const nsIFilePicker = Ci.nsIFilePicker; var fp = Cc["@mozilla.org/filepicker;1"] .createInstance(nsIFilePicker); var window = require("sdk/window/utils").getMostRecentBrowserWindow(); fp.init(window, "Select a file", nsIFilePicker.modeOpen); fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText); var rv = fp.show(); if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) { var file = fp.file; // Pega o caminho como string. Note que você normalmente não // precisará trabalhar com strings de caminho. var path = fp.file.path; // Trabalhe com o retorno de nsILocalFile... } return path; }
Função Hash
Firefox tem suporte embutido para funções hash, exposto via interface XPCOM nsICryptoHash. A página da documentação para esta interface inclui um exemplo de calculadora de hash MD5 do conteúdo do arquivo, dado seu caminho. Nós adaptamos como esta:
var {Cc, Ci} = require("chrome"); // retorna o código hexadecimal de dois dígitos para um byte function toHexString(charCode) { return ("0" + charCode.toString(16)).slice(-2); } function md5File(path) { var f = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile); f.initWithPath(path); var istream = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); // abrindo para leitura istream.init(f, 0x01, 0444, 0); var ch = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); // nós queremos usar o algoritmo MD5 ch.init(ch.MD5); // isto diz para updateFromStream ler o arquivo todo const PR_UINT32_MAX = 0xffffffff; ch.updateFromStream(istream, PR_UINT32_MAX); // passe false aqui para conseguir os dados binários de volta var hash = ch.finish(false); // converte o hash binário para hex string. var s = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join(""); return s; }
Colocando tudo junto
O add-on completo adiciona um botão ao Firfox: quando o usuário clica no botão, nós pedimos lhe para selecionar um arquivo, e registramos o hash no console:
var {Cc, Ci} = require("chrome"); // retorna o código hexadecimal de dois dígitos para um byte function toHexString(charCode) { return ("0" + charCode.toString(16)).slice(-2); } function md5File(path) { var f = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile); f.initWithPath(path); var istream = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); // abrindo para leitura istream.init(f, 0x01, 0444, 0); var ch = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); // nós queremos usar o algoritmo MD5 ch.init(ch.MD5); // isto diz para updateFromStream ler o arquivo todo const PR_UINT32_MAX = 0xffffffff; ch.updateFromStream(istream, PR_UINT32_MAX); // passe false aqui para conseguir os dados binários de volta var hash = ch.finish(false); // converte o hash binário para hex string. var s = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join(""); return s; } function promptForFile() { var window = require("sdk/window/utils").getMostRecentBrowserWindow(); const nsIFilePicker = Ci.nsIFilePicker; var fp = Cc["@mozilla.org/filepicker;1"] .createInstance(nsIFilePicker); fp.init(window, "Select a file", nsIFilePicker.modeOpen); fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText); var rv = fp.show(); if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) { var file = fp.file; // Pega o caminho como string. Note que você normalmente não // precisará trabalhar com strings de caminho. var path = fp.file.path; // Trabalhe com o retorno de nsILocalFile... } return path; } require("sdk/ui/button/action").ActionButton({ id: "show-panel", label: "Show Panel", icon: { "16": "./icon-16.png" }, onClick: function() { console.log(md5File(promptForFile())); } });
Isso funciona, mas main.js está agora ficando mais longo e sua lógica mais difícil de entender. This works , but main.js is now getting longer and its logic is harder to understand. Vamos levar os códigos do "file picker" e do "hashing code" para módulos separados.
Criando módulos separados
filepicker.js
Primeiro criamos um novo arquivo no diretório "lib" chamado "filepicker.js". Copiamos o código do seletor de arquivos, e adicionamos a seguinte linha de código no fim dele:
exports.promptForFile = promptForFile;
Isso define a interface pública do novo módulo.
Então "filepicker.js" deve parecer com isto:
var {Cc, Ci} = require("chrome"); function promptForFile() { var window = require("sdk/window/utils").getMostRecentBrowserWindow(); const nsIFilePicker = Ci.nsIFilePicker; var fp = Cc["@mozilla.org/filepicker;1"] .createInstance(nsIFilePicker); fp.init(window, "Select a file", nsIFilePicker.modeOpen); fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText); var rv = fp.show(); if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) { var file = fp.file; // Get the path as string. Note that you usually won't // need to work with the string paths. var path = fp.file.path; // work with returned nsILocalFile... } return path; } exports.promptForFile = promptForFile;
md5.js
Próximo, crie um outro arquivo no "lib", chamado "md5.js". Copie o código do hashing, e adicione esta linha ao seu fim:
exports.hashFile = md5File;
O arquivo completo parece com isto:
var {Cc, Ci} = require("chrome"); //retorna o código hexadecimal de dois dígitos para um byte function toHexString(charCode) { return ("0" + charCode.toString(16)).slice(-2); } function md5File(path) { var f = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile); f.initWithPath(path); var istream = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); // abrindo para leitura istream.init(f, 0x01, 0444, 0); var ch = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); // nós queremos usar o algoritmo MD5 ch.init(ch.MD5); // isto diz para updateFromStream ler o arquivo todo const PR_UINT32_MAX = 0xffffffff; ch.updateFromStream(istream, PR_UINT32_MAX); // passe false aqui para conseguir os dados binários de volta var hash = ch.finish(false); // converte o hash binário para hex string. var s = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join(""); return s; } exports.hashFile = md5File;
main.js
Finalmente, atualizamos o main.js para importar estes dois módulos e usá-los:
var filepicker = require("./filepicker.js"); var md5 = require("./md5.js"); require("sdk/ui/button/action").ActionButton({ id: "show-panel", label: "Show Panel", icon: { "16": "./icon-16.png" }, onClick: function() { console.log(md5.hashFile(filepicker.promptForFile())); } });
Você pode distribuir estes módulos para outros desenvolvedores, também. Eles podem copia-los em algum lugar do add-on, e inclui-los usando require()
do mesmo modo.
Aprendendo Mais
Para ver alguns módulos que as pessoas já desenvolveram, veja a página community-developed. Para aprender como usar módulos de terceiros em seu próprio código, veja o tutorial adicionando itens de menu.