Skip to content Skip to sidebar Skip to footer

Expose File Writing To A Webpage With A Firefox Extension

I have a web application that my client uses for the cash registry. What I need to do is to create a local file as the cash register's software needs to read from that file in orde

Solution 1:

Sorry about the super-late reply.

If I understand your question correctly, you have a P.O.S application that runs in Firefox talking to some sort of local webserver via HTTP. The client-side JavaScript of your application needs to be able to read & write files from the local filesystem of the browser's PC.

If that's correct, then you can do so as follows. You'll need to create a Firefox addon, the simpliest kind of which is called a "bootstrapped" (or "restartless") addon.


A restartless addon consists of two files:

  • bootstrap.js (The JavaScript file containing your 'privileged' code)
  • install.rdf (an XML file describing your addon to Firefrox)

To build the addon, simply place both files inside the top-level (no folders!) of a ZIP file with the file extension .xpi. To install the addon, navigate to about:addons then from the tools menu, click Install from file, find your XPI, open it, then after a short delay choose Install.


In install.rdf put something like this:

<?xml version="1.0"?><RDFxmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"xmlns:em="http://www.mozilla.org/2004/em-rdf#"><Descriptionabout="urn:mozilla:install-manifest"><em:id>youraddonname@yourdomain</em:id><em:type>2</em:type><em:name>Name of your addon</em:name><em:version>1.0</em:version><em:bootstrap>true</em:bootstrap><em:description>Describe your addon.</em:description><em:creator>Your name</em:creator><!-- Firefox Desktop --><em:targetApplication><Description><em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id><em:minVersion>4.0.*</em:minVersion><em:maxVersion>29.0.*</em:maxVersion></Description></em:targetApplication></Description></RDF>

You need to implement two mandatory JavaScript functions in the bootstrap.js:

  • startup() - called when you install the addon, and when your browser starts up.
  • shutdown() - called when you uninstall the addon, and when your browser shuts down.

You should call the rest of your 'privileged' code in startup(). For hygiene, you can (and probably should) also implement install() and uninstall() functions.

Start by implementing the following code in bootstrap.js:

constCc = Components.classes;
constCi = Components.interfaces;
let consoleService = Cc["@mozilla.org/consoleservice;1"]
                        .getService(Ci.nsIConsoleService);
let wm             = Cc["@mozilla.org/appshell/window-mediator;1"]
                        .getService(Ci.nsIWindowMediator);

functionLOG(msg) {
  consoleService.logStringMessage("EXTENSION: "+msg);    
}

functionstartup() {
  try {
    LOG("starting up...");
    let windows = wm.getEnumerator("navigator:browser");
    while (windows.hasMoreElements()) {
      let chromeWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
      WindowListener.setupBrowserUI(chromeWindow);
    }
    wm.addListener(WindowListener);
    LOG("done startup.");
  } catch (e) {
    LOG("error starting up: "+e);
  }
}

functionshutdown() {
  try {
    LOG("shutting down...");
    let windows = wm.getEnumerator("navigator:browser");
    while (windows.hasMoreElements()) {
      let chromeWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
      WindowListener.tearDownBrowserUI(chromeWindow);
    }
    wm.addListener(WindowListener);
    LOG("done shutdown.");
  } catch (e) {
    LOG("error shutting down: "+e);
  }
}

Basically, that calls WindowListener.setupBrowserUI() for each current & future window of your web-browser. WindowListener is defined as follows:

varWindowListener = {
  setupBrowserUI: function(chromeWindow) {
    chromeWindow.gBrowser.addEventListener('load', my_load_handler, true);
  },
  tearDownBrowserUI: function(chromeWindow) {
    chromeWindow.gBrowser.removeEventListener('load', my_load_handler, true);
  },
  onOpenWindow: function(xulWindow) {
    let chromeWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindow);
    chromeWindow.addEventListener("load", functionlistener() {
      chromeWindow.removeEventListener("load", listener, false);
      var domDocument = chromeWindow.document.documentElement;
      var windowType = domDocument.getAttribute("windowtype");
      if (windowType == "navigator:browser")
        WindowListener.setupBrowserUI(chromeWindow);
    }, false);
  },
  onCloseWindow: function(chromeWindow) { },
  onWindowTitleChange: function(chromeWindow, newTitle) { }
};

That sets up an event listener for the OpenWindow event, and in turn installs an event listener for load events in the TabBrowser of each ChromeWindow. The load event handler is defined as:

var my_load_handler = function (evt) {
  try {
    var browserEnumerator = wm.getEnumerator("navigator:browser");
    while (browserEnumerator.hasMoreElements()) {
      var browserWin = browserEnumerator.getNext();
      var tabbrowser = browserWin.gBrowser;
      var numTabs = tabbrowser.browsers.length;
      for (var index = 0; index < numTabs; index++) {
        var currentBrowser = tabbrowser.getBrowserAtIndex(index);
        var domWindow = currentBrowser.contentWindow.wrappedJSObject;
        // identify your target page(s)...if (domWindow.location.href == 'http://yourserver/yourpage') {
          // install the privileged methods (if not already there)if (!domWindow.hasOwnProperty('__my_priv_members__') {
            install_my_privileged_methods(browserWin, domWindow);
          }
        }
      }
    }
  } catch (e) {
    LOG(e);
  }
}

That targets the correct pages (by checking the window.location.href and calls install_my_privileged_methods on their window object, which is defined as:

functioninstall_my_privileged_methods(chromeWindow, domWindow) {
  install_privileged_method(chromeWindow, domWindow, 'WriteFile', 
    function(priv) {
      returnfunction(File, Text, cb) {
        priv.call([File, Text], function(rstatus, rdata, rerror){
          if (cb) cb(rstatus, rerror);
        });
      };
    },
    function (chromeWindow, args, cb) {
      var [File, Text] = args;
      if (!File) returncb(0, null, "need a filename");
      try {
        const unicodeConverter = 
           Cc["@mozilla.org/intl/scriptableunicodeconverter"]
             .createInstance(Ci.nsIScriptableUnicodeConverter);
        unicodeConverter.charset = "UTF-8";
        Text = unicodeConverter.ConvertFromUnicode(Text);
        const os = Cc["@mozilla.org/network/file-output-stream;1"]
                     .createInstance(Ci.nsIFileOutputStream);
        os.init(File, 0x02 | 0x08 | 0x20, 0700, 0);
        os.write(Text, Text.length);
        os.close();
        cb(1, null, null);
      } catch (e) {
        cb(0, null, "error writing file: "+e);
      }
    }
  );

  install_privileged_method(chromeWindow, domWindow, 'ReadFile', 
    function(priv) {
      returnfunction(File, cb) {
        priv.call([File], function(rstatus, rdata, rerror){
          if (cb) cb(rstatus, rdata, rerror);
        });
      };
    },
    function (chromeWindow, args, cb) {
      var [File] = args;
      if (!File) returncb(0, null, "need a filename");
      try {
        const is = Cc["@mozilla.org/network/file-input-stream;1"]
                     .createInstance(Ci.nsIFileInputStream);
        const sis = Cc["@mozilla.org/scriptableinputstream;1"]
                      .createInstance(Ci.nsIScriptableInputStream);
        is.init(File, 0x01, 0400, null);
        sis.init(is);
        varText = sis.read(sis.available());    
        is.close();
        cb(1, Text, null);
      } catch (e) {
        cb(0, null, "error reading file: "+e);
      }
    }
  );
}

I didn't test this code. It's a straigh-forward translation of what you wrote above... I'll assume that works!

That add two special methods, WriteFile & ReadFile, to the chosen window objects. In your web application's (unprivileged) JavaScript code use them like this:

var buffer = '...'; // the text to be writtenwindow.WriteFile('C:\\path\\to\\file.txt', buffer, function(ok, errmsg) {
  if (!ok) alert(errmsg);
});

window.ReadFile('C:\\path\\to\\file.txt', function(ok, buffer, errmsg) { 
  if (!ok) returnalert(errmsg);
  // use buffer here!
});

Finally, install_privileged_method is defined as:

var install_privileged_method = (function(){
  var gensym = (function (){
    var __sym = 0;
    returnfunction () { return'__sym_'+(__sym++); }
  })();
  returnfunction (chromeWindow, target, slot, handler, methodFactory) {
    try {
      target.__pmcache__ = target.hasOwnProperty('__pmcache__')
        ? target.__pmcache__
        : { ticket_no: 0, callbacks: {}, namespace: gensym() };
      target[slot] = methodFactory({ call: function(fargs, fcb) {
        try {
          var ticket_no = target.__pmcache__.ticket_no++;
          target.__pmcache__.callbacks[ticket_no] = fcb;
          var cevent = target.document.createEvent("CustomEvent");
          cevent.initCustomEvent(
            target.__pmcache__.namespace+'.'+slot,
            true, true, { fargs: fargs, ticket_no: ticket_no }
          );
          target.dispatchEvent(cevent);
        } catch (ue) {
          fcb(0, null, 'untrusted dispatcher error: '+ue);
        }
      }});
      LOG("installed untrusted dispatcher for method '"+slot+"'.");
      target.addEventListener(
        target.__pmcache__.namespace+'.'+slot,
        function(cevent){
          var ticket_no = cevent.detail.ticket_no;
          var fargs = cevent.detail.fargs;
          var fcb = target.__pmcache__.callbacks[ticket_no];
          try {
            handler(chromeWindow, fargs, fcb);
          } catch (pe) {
            fcb(0, null, 'privileged handler error: '+pe);
          }
        },
        false,
        true
      );
      LOG("installed privileged handler for method '"+slot+"'.");
    } catch (ie) {
      LOG("ERROR installing handler/factory for privileged "+
          "method '"+slot+"': "+ie);
    }
  };
})();

Post a Comment for "Expose File Writing To A Webpage With A Firefox Extension"