2

I'm trying to get an arguments of firefox command line in my extension:

'firefox.exe -viewapp url -another-command -blablala:somedata'

And here I have found an example

But I dont get it how can I get all arguments on first start of browser. I need to display it in web console something like this:

console.log(commandLine.arguments)
// -viewapp url -another-command -blablala:somedata

Please help

Macedonian
  • 47
  • 11

2 Answers2

4

I see you just want to see what command line arguments it was launched with. I know a js-ctypes solution. That will require you to code for the different platforms. Here's a start, it gets you the arguments on windows and mac. I didnt do the linux part yet:

Cu.import('resource://gre/modules/osfile.jsm');
Cu.import('resource://gre/modules/ctypes.jsm');

var lib 
var osname = OS.Constants.Sys.Name.toLowerCase();
if (osname.indexOf('win') === 0) {
    // windows
    lib = ctypes.open('kernel32');
    var GetCommandLineW = lib.declare('GetCommandLineW', ctypes.winapi_abi,  ctypes.jschar.ptr);
    var rez = GetCommandLineW();
    console.log('rez:', rez.readString());
} else if (osname == 'darwin') {
    // mac

    lib = ctypes.open(ctypes.libraryName('objc'));

    // BASIC TYPES
    var BOOL = ctypes.signed_char;
    var NSUINTEGER = ctypes.unsigned_long;

    // COMMON FUNCTIONS
    var objc_getClass = lib.declare('objc_getClass', ctypes.default_abi, ctypes.voidptr_t, ctypes.char.ptr);
    var objc_msgSend = lib.declare('objc_msgSend', ctypes.default_abi, ctypes.voidptr_t, ctypes.voidptr_t, ctypes.voidptr_t, '...');
    var sel_registerName = lib.declare('sel_registerName', ctypes.default_abi, ctypes.voidptr_t, ctypes.char.ptr);

    // current_application = [NSRunningApplication currentApplciation];
    var NSProcessInfo = objc_getClass('NSProcessInfo');
    var processInfo = sel_registerName('processInfo');
    var process_info = objc_msgSend(NSProcessInfo, processInfo);

    // bundle_identifier = [current_application bundleIdentifier]
    var arguments = sel_registerName('arguments');
    var args = objc_msgSend(process_info, arguments);


    var count = sel_registerName('count');
    var _count = objc_msgSend(args, count);
    console.info('_count:', _count, _count.toString(), uneval(_count), parseInt(_count));

    // make it into a js number
    _count = parseInt(ctypes.cast(_count, NSUINTEGER).value); 
    console.log('_count:', _count);

    var objectAtIndex = sel_registerName('objectAtIndex:'); // used in for loop
    var UTF8String = sel_registerName('UTF8String'); // used in for loop

    for (var i=0; i<_count; i++) {
        var arg = objc_msgSend(args, objectAtIndex, NSUINTEGER(i)); // must wrap `i` in NSUINTEGER as its variadic, you have to specify the type. this is specific to js-ctypes         
        var argString = ctypes.cast(objc_msgSend(arg, UTF8String), ctypes.char.ptr);
        console.log('arg "' + i + '":', argString.readStringReplaceMalformed(), 'arg:', arg, arg.toString(), uneval(arg));
    }

} else {
    // *nix

    // todo
    // per - http://stackoverflow.com/a/821889/1828637
    var libcPaths = ['libc.so', 'libc.so.7', 'libc.so.61.0', 'libc.so.6', 'libc.so.0.1', 'libc.dylib'];
    for (var i=0; i<libcPaths.length; i++) {
        try {
            lib = ctypes.open(libcPaths[i]);
            break;
        } catch(ignore) {}
    }
    if (!lib) {
        throw new Error('failed to find libc on this system');
    }

    var popen = lib.declare('popen', ctypes.default_abi, ctypes.voidptr_t, ctypes.char.ptr, ctypes.char.ptr);
    var fread = lib.declare('fread', ctypes.default_abi, ctypes.size_t, ctypes.voidptr_t, ctypes.size_t, ctypes.size_t, ctypes.voidptr_t);
    var feof = lib.declare('feof', ctypes.default_abi, ctypes.int, ctypes.voidptr_t);
    var pclose = lib.declare('pclose', ctypes.default_abi, ctypes.int, ctypes.voidptr_t);
    var getpid = lib.declare('getpid', ctypes.default_abi, ctypes.int32_t);

    // ps -fp 2540
    var pid = getpid();
    var file = popen('ps -ww -fp' + pid, 'r');

    var buf = ctypes.char.array(100)();
    var jsbuf = [];
    var reachedEof = false;
    while (!reachedEof) {
        var cnt = fread(buf, 1, buf.length * buf.constructor.elementType.size, file);
        cnt = parseInt(cnt);
        if (cnt > 0) {
            jsbuf.push(buf.readString().substring(0, cnt));
        }
        reachedEof = feof(file);
    }

    var closeit = pclose(file);
    console.log('closeit:', closeit);

    console.log('jsbuf:', jsbuf.join(''));
}

lib.close();
Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • Oh yes! This is it! // you need to correct a mistake in the code: var GetCommandLineW = kernel32.declare('GetCommandLineW', ctypes.winapi_abi, ctypes.jschar.ptr); // to: var GetCommandLineW = lib.declare('GetCommandLineW', ctypes.winapi_abi, ctypes.jschar.ptr); – Macedonian Apr 29 '16 at 13:40
  • @jazzperio Nice :) If you need help on the linux method let me know. I'm kind of curious to see how its done on *nix – Noitidart Apr 29 '16 at 13:42
  • Ok, next time I will ask you. Thank you so much, you really helped me a lot! – Macedonian Apr 29 '16 at 13:47
  • @Jazzperio I added in the linux method per http://stackoverflow.com/a/821889/1828637 - but i dont really like how i did it. I used `popen` to execute `ps`. I would prefer a more direct way, that doesn't use `popen` to call another process/op. Also touched up the mac code so its cleaner. – Noitidart Apr 30 '16 at 13:30
  • I am not expert on Linux, but I think it might be a bash file, which as a result returns a single row. And maybe it needs root or sudo privileges – Macedonian Apr 30 '16 at 15:47
  • @Jazzperio it works fine without sudo. It's just opening another process and doing `ps`. It's like a bash file, but it's a shell file to be exact. It excecutes it with `/bin/sh` with `-c`. That's why I don't like it so much, we're spawning a seperate process to get simple info. So there has to be a direct/more efficient/better cross linux way. But this works for now. :) – Noitidart Apr 30 '16 at 15:53
  • 1
    much simpler approach for linux: read from `/proc/self/cmdline` – the8472 May 02 '16 at 16:13
  • Thanks @the8472 - i read that the proc/self method is not cross linux compatible. They mention that ps is guranteed though that's why I went with that. – Noitidart May 02 '16 at 16:15
  • @the8472 it was the most up voted (142 votes) solution here - http://stackoverflow.com/a/821889/1828637 – Noitidart May 02 '16 at 16:20
  • 1
    I see no mention of `/proc/self` not working in that answer. It mentions other unixes, but we're discussing *"not cross __linux__ compatible"*. – the8472 May 02 '16 at 16:24
  • 1
    also, if you want to run `ps`, you can just use the sdk's process spawning module, no need for js-ctypes. – the8472 May 02 '16 at 16:29
  • @the8472 oh i see. i mispoke then. my solution above is meant for cross unix/linux. child_process is nice but i timed it, it always clocks in slower then `popen` i have no idea why. – Noitidart May 02 '16 at 16:32
  • 1
    shouldn't really matter since you only need to get the commandline once. it doesn't change after all. so you can use that module to make the code simpler. – the8472 May 02 '16 at 16:36
  • True, up voted for that idea. I'll leave this way in though in case he wants to use it from a ChromeWorker. I run all my codes from workers. I wish we could use subprocess in workers, but they have some xpcom for constants. subprocess is jsctypes too so should work perfectly fine from worker, if it was for those constants they called. i hope they bring a worker version. – Noitidart May 02 '16 at 17:01
3

Here is an extremely simple addon that does this:

https://github.com/Noitidart/Restore--remote/blob/master/bootstrap.js

This addon here is a bootstrap (so restartless - which is good) addon. So you have to tap into low level stuff if you use SDK. It also doesn't require a chrome.manifest file like the one on MDN you linked to.

Here is the addon on the marketplace - https://addons.mozilla.org/en-US/firefox/addon/restore-remote/

Here is the code:

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cm = Components.manager;
const Cu = Components.utils;
const Cr = Components.results;

Cm.QueryInterface(Ci.nsIComponentRegistrar);

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Preferences.jsm');

function RemoteCommandLine(target, url, cl) {
  this._target = target;
  this._url = url;
  this._cl = cl;
}

RemoteCommandLine.prototype = {
  get preventDefault() {
    return this._cl.preventDefault;
  }, 
  set preventDefault(value) {
    this._cl.preventDefault = value;
  },
  handleFlag: function(flag, caseSensitive) {
    return false;
  },
  handleFlagWithParam: function(flag, caseSensitive) {
    if (!(flag == 'new-tab' && this._target == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) &&
        !(flag == 'new-window' && this._target == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW))
      return null;
    var url = this._url;
    this._url = null;
    return url
  },
  resolveURI: function(url) {
    return this._cl.resolveURI(url);
  },
};

function Remote() {}

Remote.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
  classDescription: 'remote',
  classID: Components.ID('{1280e159-cac2-4188-af5a-e6089527b7b8}'),
  contractID: '@mozilla.org/commandlinehandler/general-startup;1?type=remote',

  handle: function(cmdLine)
  {
    try {
      var remoteCommand = cmdLine.handleFlagWithParam("remote", true);
    }
    catch (e) {
      throw Cr.NS_ERROR_ABORT;
    }

    if (remoteCommand != null) {
      try {
        var a = /^\s*(\w+)\(([^\)]*)\)\s*$/.exec(remoteCommand);
        var remoteVerb;
        if (a) {
          remoteVerb = a[1].toLowerCase();
          var remoteParams = [];
          var sepIndex = a[2].lastIndexOf(",");
          if (sepIndex == -1)
            remoteParams[0] = a[2];
          else {
            remoteParams[0] = a[2].substring(0, sepIndex);
            remoteParams[1] = a[2].substring(sepIndex + 1);
          }
        }

        switch (remoteVerb) {
        case "openurl":
        case "openfile":
          // openURL(<url>)
          // openURL(<url>,new-window)
          // openURL(<url>,new-tab)

          // First param is the URL, second param (if present) is the "target"
          // (tab, window)
          var url = remoteParams[0];
          var target = Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW;
          if (remoteParams[1]) {
            var targetParam = remoteParams[1].toLowerCase()
                                             .replace(/^\s*|\s*$/g, "");
            if (targetParam == "new-tab")
              target = Ci.nsIBrowserDOMWindow.OPEN_NEWTAB;
            else if (targetParam == "new-window")
              target = Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW;
            else {
              // The "target" param isn't one of our supported values, so
              // assume it's part of a URL that contains commas.
              url += "," + remoteParams[1];
            }
          }

          if (target == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
            target = Preferences.get('browser.link.open_newwindow',
                                     Ci.nsIBrowserDOMWindow.OPEN_NEWTAB);
          }

          var clh = Cc['@mozilla.org/browser/clh;1'].getService(Ci.nsICommandLineHandler);
          clh.handle(new RemoteCommandLine(target, url, cmdLine));
          break;


        case "ping":
          break;
        default:
          // Somebody sent us a remote command we don't know how to process:
          // just abort.
          throw "Unknown remote command.";
        }

        cmdLine.preventDefault = true;
      }
      catch (e) {
        Components.utils.reportError(e);
        // If we had a -remote flag but failed to process it, throw
        // NS_ERROR_ABORT so that the xremote code knows to return a failure
        // back to the handling code.
        throw Cr.NS_ERROR_ABORT;
      }
    }
  },
};

const RemoteFactory = XPCOMUtils.generateNSGetFactory([Remote])(Remote.prototype.classID);

function startup(aData, aReason) {
  Cm.registerFactory(Remote.prototype.classID,
                     Remote.prototype.classDescription,
                     Remote.prototype.contractID,
                     RemoteFactory);
  var catman = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
  catman.addCategoryEntry('command-line-handler', 'l-remote', Remote.prototype.contractID, false, true);
}

function shutdown(aData, aReason) {
  var catman = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
  catman.deleteCategoryEntry('command-line-handler', 'l-remote', false);
  Cm.unregisterFactory(Remote.prototype.classID, RemoteFactory);
}
Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • I think that's not what I want. I don't need an instance of command line, I need to get first arguments on starting firefox. – Macedonian Apr 29 '16 at 12:12
  • @jazzperio oh I see. This is easily possible with js-ctypes. You just want to read what arguments it was launched with right? What platform are you on? I have a ready made example for mac - https://gist.github.com/Noitidart/72d379828e0a3f98b5d6 - i can help you knock that up for Windows and Linux as well. – Noitidart Apr 29 '16 at 13:13
  • How would you adapt this code in 2022? The addon does not exist anymore – tobiasBora Sep 12 '22 at 22:36