Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
331 views
in Technique[技术] by (71.8m points)

javascript - 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 order to print.

Until now i was using this code:

netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(filePath);

Unfortunately with the latest version of firefox this isn't working anymore so I was told that i need and add-on to create the file.I've tried to develop an add-on(don't know if succesfully) and i have main.js looking like this :

var FileManager =
{
Write:
    function (File, Text)
    {
        if (!File) return;
        const unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
            .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);

        unicodeConverter.charset = "UTF-8";

        Text = unicodeConverter.ConvertFromUnicode(Text);
        const os = Components.classes["@mozilla.org/network/file-output-stream;1"]
          .createInstance(Components.interfaces.nsIFileOutputStream);
        os.init(File, 0x02 | 0x08 | 0x20, 0700, 0);
        os.write(Text, Text.length);
        os.close();
    },

Read:
    function (File)
    {
        if (!File) return;
        var res;

        const is = Components.classes["@mozilla.org/network/file-input-stream;1"]
            .createInstance(Components.interfaces.nsIFileInputStream);
        const sis = Components.classes["@mozilla.org/scriptableinputstream;1"]
            .createInstance(Components.interfaces.nsIScriptableInputStream);
        is.init(File, 0x01, 0400, null);
        sis.init(is);

        res = sis.read(sis.available());

        is.close();

        return res;
    },
}; 

Any ideas how should I use main.js?Where I find it after the add-on is installed? I need to use something like this : FileManager.Write(path,text).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
    <Description about="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:

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

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

function startup() {
  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);
  }
}

function shutdown() {
  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:

var WindowListener = {
  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", function listener() {
      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:

function install_my_privileged_methods(chromeWindow, domWindow) {
  install_privileged_method(chromeWindow, domWindow, 'WriteFile', 
    function(priv) {
      return function(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) return cb(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) {
      return function(File, cb) {
        priv.call([File], function(rstatus, rdata, rerror){
          if (cb) cb(rstatus, rdata, rerror);
        });
      };
    },
    function (chromeWindow, args, cb) {
      var [File] = args;
      if (!File) return cb(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);
        var Text = 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 written
window.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) return alert(errmsg);
  // use buffer here!
});

Finally, install_privileged_method is defined as:

var install_privileged_method = (function(){
  var gensym = (function (){
    var __sym = 0;
    return function () { return '__sym_'+(__sym++); }
  })();
  return function (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);
    }
  };
})();

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...