Criando Módulos Reutilizáveis

Para seguir este tutorial você precisa do SDK instalado e conhecimento básico de cfx.

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.

Etiquetas do documento e colaboradores

Etiquetas: 
 Última atualização por: arai,