CyclingBloke
CyclingBloke

Reputation: 150

OS.File in Firefox SDK Add-on

This is my first attempt at writing a Firefox add-on.

I'm trying to read a text file that is contained in an Firefox Add-on I'm writing.

I'm following the example from MDN

let decoder = new TextDecoder();      // This decoder can be reused for several reads
let array = OS.File.read("file.txt"); // Read the complete file as an array
let text = decoder.decode(array);     // Convert this array to a text

In my main.js I have the following snippet of code :

Components.utils.import("resource://gre/modules/osfile.jsm");

var pathFile = OS.Path.join("_locales", "en", "messages.json");        

let decoder = new TextDecoder('utf-8');

let promise = OS.File.read(pathFile);        
promise = promise.then(
  function onSuccess(array) {
    return decoder.decode(array);
  },
  function onReject(array) {
    console.log("onReject read: ");
  }            
);

When I run the add-on using the cfx run command, I get the following error:

Message: ReferenceError: TextDecoder is not defined

I'm currently using Firefox 30, and the Firefox Add-on SDK 1.16.

Should I not use OS.File in main.js in an add-on?

Should I use FileUtils instead, i.e. Components.utils.import("resource://gre/modules/FileUtils.jsm");?

Upvotes: 2

Views: 1846

Answers (2)

nmaier
nmaier

Reputation: 33162

First of all, you linked the OS.File for Workers-docs. You don't use it in a worker (no new Worker anywhere), but the main thread, so you'll instead want to refer to OS.File for the main thread-docs.

SDK modules unfortunately don't get TextEncoder/TextDecoder automatically right now. As @erikvold suggests, you could:

const { Buffer, TextEncoder, TextDecoder } = require('sdk/io/buffer');

or even get it from osfile.jsm directly:

const { Cc, Ci, Cu } = require("chrome");
const { OS, TextEncoder, TextDecoder } = Cu.import("resource://gre/modules/osfile.jsm", {});

Now back to your original question:

In general OS.File can be used in SDK add-ons and is a good way to read/write arbitrary file in the file system.

However OS.File, FileUtils, sdk/io/... are not suited for reading files in your add-on, because usually those files won't be located directly in the user's file system, but instead be located within the installed XPI file (which is merely a zip file) and these APIs allow you to read files verbatim, but don't parse the container zip files (XPI) for you.

You have a couple of options, though:

  • The file name seems to indicate that you want to localize something. The SDK comes with the l10n module exactly for this purpose.
  • Stuff located in the data/ folder of your add-on can also be read with self.data.load(), but you still need to decode/parse the data.
  • You can read files packaged in your add-on (XPI) with XMLHttpRequest, which the SDK makes available in the net/xhr module. As an added bonus, you can have the XHR object decode and/or parse the file for you by setting an appropriate .responseType value.

Constructing the URI for XHR is easy if the file to read is located in the data/ folder of your add-on, e.g.

cont self = require("sdk/self");
var uri = self.data.url("en/messages.json");
// would map to data/en/messages.json

Otherwise, there isn't really an official, supported way to get URIs, but e.g. the following will work for lib/ (at the moment, but might fail in the future).

cont self = require("sdk/self");
var uri = self.data.url("../lib/main.js");
// would map to data/../lib/main.js -> lib/main.js

Placing your own files somewhere else is tricky, as the SDK will only package certain paths when building the XPI; in general lib/, data/, locale/ (see l10n), chrome/ (see XUL Migration Guide)

Here is a minimal example using XHR (but remember that for localization there is the l10n module already):

const self = require("sdk/self");
const { XMLHttpRequest } = require("sdk/net/xhr");

// Assume data/some.json is:
// {"abc":123}
var req = new XMLHttpRequest();
req.onload = function() {
  // req.response is an object containing the parsed JSON.
  console.log(req.response.abc); // 123
};
req.open("GET", self.data.url("some.json"));
req.responseType = "json";
req.send();

PS: When working with OS.File in particular and promises in general, consider using Task.jsm which can make it much easier to write and re-read (maintain) code.

Upvotes: 3

erikvold
erikvold

Reputation: 16508

Try

const { Buffer, TextEncoder, TextDecoder } = require('sdk/io/buffer');
const { OS } = require("resource://gre/modules/osfile.jsm");

var pathFile = OS.Path.join("_locales", "en", "messages.json");        

let decoder = new TextDecoder('utf-8');

let promise = OS.File.read(pathFile);        
promise = promise.then(function onSuccess(array) {
  return decoder.decode(array);
}, function onReject(array) {
  console.log("onReject read: ");
});

Upvotes: 2

Related Questions