Search code examples
javascriptfirefox-addonsidebarfirefox-addon-restartless

How to get URL from current active Browser window/tab from within a script running in an XUL <browser> (e.g. in a sidebar)


I am creating a simple bootstrapped add-on for Firefox. I need to capture the current URL from the browser through sidebar with a button click.

My bootstrap.js:

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import("resource://gre/modules/NetUtil.jsm");  
Cu.import("resource://gre/modules/FileUtils.jsm");

/*start - windowlistener*/
var windowListener = {
    //DO NOT EDIT HERE
    onOpenWindow: function (aXULWindow) {
        // Wait for the window to finish loading
        let aDOMWindow =aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIDOMWindowInternal||Ci.nsIDOMWindow);
        aDOMWindow.addEventListener("load", function () {
            aDOMWindow.removeEventListener("load", arguments.callee, false);
            windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
        }, false);
    },
    onCloseWindow: function (aXULWindow) {},
    onWindowTitleChange: function (aXULWindow, aNewTitle) {},
    register: function () {
        // Load into any existing windows
        let XULWindows = Services.wm.getXULWindowEnumerator(null);
        while (XULWindows.hasMoreElements()) {
            let aXULWindow = XULWindows.getNext();
            let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
            windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
        }
        // Listen to new windows
        Services.wm.addListener(windowListener);
    },
    unregister: function () {
        // Unload from any existing windows
        let XULWindows = Services.wm.getXULWindowEnumerator(null);
        while (XULWindows.hasMoreElements()) {
            let aXULWindow = XULWindows.getNext();
            let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
            windowListener.unloadFromWindow(aDOMWindow, aXULWindow);
        }
        //Stop listening so future added windows dont get this attached
        Services.wm.removeListener(windowListener);
    },
    //END - DO NOT EDIT HERE
    loadIntoWindow: function (aDOMWindow, aXULWindow) {
        if (!aDOMWindow) {
            return;
        }

        //START - EDIT BELOW HERE
        var browser = aDOMWindow.document.querySelector('#browser')
        if (browser) {
            var splitter = aDOMWindow.document.createElement('splitter');
            var propsToSet = {
                id: 'demo-sidebar-with-html_splitter',
                //I'm just copying what Mozilla does for their social sidebar splitter 
                //  I left it out, but you can leave it in to see how you can style
                //  the splitter
                class: 'sidebar-splitter'
            }
            for (var p in propsToSet) {
                splitter.setAttribute(p, propsToSet[p]);
            }

            var sidebar = aDOMWindow.document.createElement('vbox');
            var propsToSet = {
                id: 'demo-sidebar-with-html_sidebar',
                //Mozilla uses persist width here, I don't know what it does and can't
                //  see it how makes a difference so I left it out
                //persist: 'width' 
            }
            for (var p in propsToSet) {
                sidebar.setAttribute(p, propsToSet[p]);
            }
            var htmlVal = loadJsonHTML(0);
            var sidebarBrowser = aDOMWindow.document.createElement('browser');
            var propsToSet = {
                id: 'demo-sidebar-with-html_browser',
                type: 'content',
                context: 'contentAreaContextMenu',
                disableglobalhistory: 'true',
                tooltip: 'aHTMLTooltip',
                autoscrollpopup: 'autoscroller',
                flex: '1', //do not remove this
                //you should change these widths to how you want
                style: 'min-width: 14em; width: 18em; max-width: 36em;', 
                //or just set this to some URL like http://www.bing.com/
                src: 'data:text/html,'+ htmlVal
            }
            for (var p in propsToSet) {
                sidebarBrowser.setAttribute(p, propsToSet[p]);
            }

            browser.appendChild(splitter);

            sidebar.appendChild(sidebarBrowser);
            browser.appendChild(sidebar);
        }
        //END - EDIT BELOW HERE
    },
    unloadFromWindow: function (aDOMWindow, aXULWindow) {
        if (!aDOMWindow) {
            return;
        }
        //START - EDIT BELOW HERE
        var splitter = aDOMWindow.document
                                 .querySelector('#demo-sidebar-with-html_splitter');

        if (splitter) {
            var sidebar = aDOMWindow.document
                                    .querySelector('#demo-sidebar-with-html_sidebar');
            splitter.parentNode.removeChild(splitter);
            sidebar.parentNode.removeChild(sidebar);
        }
        //END - EDIT BELOW HERE
    }
};
/*end - windowlistener*/

function startup(aData, aReason) {
    windowListener.register();
}

function shutdown(aData, aReason) {
    if (aReason == APP_SHUTDOWN) return;
    windowListener.unregister();
}

function loadJsonHTML(val=0){

    var fileContent = "";   
    var localFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);

    //full path is okay if directory exists     
    localFile.initWithPath("/Users/tinuy/Desktop/test_addodn/input.txt");

    //otherwise specify directory, create it if necessary, and append leaf.
    //localFile.initWithPath("C:\Users\tinuy\Documents\test\input.txt");

    if ( localFile.exists() == false ) {
        fileContent = "File does not exist";
    }
    var istream = Cc["@mozilla.org/network/file-input-stream;1"]
                  .createInstance(Ci.nsIFileInputStream);
    istream.init(localFile, 0x01, 4, null);
    var fileScriptableIO = Cc["@mozilla.org/scriptableinputstream;1"]
                           .createInstance(Ci.nsIScriptableInputStream); 
    fileScriptableIO.init(istream);
    // parse the XML into our internal document
    istream.QueryInterface(Ci.nsILineInputStream); 
    //fileContent = fileScriptableIO.read( '1' );
    var csize = 0; 
    while ((csize = fileScriptableIO.available()) != 0) {
        fileContent += fileScriptableIO.read( csize );
    }
    var array = fileContent.split("&");
    fileScriptableIO.close();   
    istream.close();

    return makeHTML(array[val], val);
}

function makeHTML(value, key){
    var arrValues = value.split(",");
    var htmlContent = '<div name="content" class="content">' +
        '<p> Name :' + arrValues[0] + '</p>';
    htmlContent += '<p> Price :' + arrValues[2] + '</p>';
    htmlContent += '<p> Color :' + arrValues[3] + '</p>';
    htmlContent += '<p> UID :' + arrValues[1] + '</p>';
    htmlContent += '<p><input type="radio" name="valid" value="yes" />Yes &nbsp;&nbsp; ' +
        '<input type="radio" name="valid" value="no" /> No</p>' +
        '<p><input type="text" placeholder="Reason" name="checkreason"></p>' +
        '<p><input type="text" placeholder="Feedback" name="feedback"></p>' +
        '</div><div><button name="load" type="button" id="loadit" onclick="loadHtml()" ' + 
        'loadurl="'+arrValues[4]+'">Load</button> <button name="save" type="button">' + 
        'Save </button> <button name="next" type="button" key="'+key+'">Next </button> ' +
        '</div> <script> function loadHtml() {' + 
            'var a = gBrowser.contentWindow.location.href ;alert(a);' + 
        '} </script>';
    return htmlContent;
}

function install() {}

function uninstall() {}

I tried all suggestions from Get current page URL from a firefox sidebar extension but nothing worked.


Solution

  • However, from the fact that you have set:

    var propsToSet = {
        id: 'demo-sidebar-with-html_browser',
        type: 'content', //<---------------------------This
    

    the <browser> type to content, I am assuming one of two things:

    1. That you are not actually trying to get the active tab's URI from within the code you are loading into the <browser> through the src attribute.
      • If this is the case, please see my answer to "How to Get Active Tab Location by e10s add-on". In that case, the context from which you are running should allow you to use the code in that answer.
      • This possible assumption, #1, is not the case for your code.
    2. You want to gain access to the URI form code within the <browser> you have loaded into the sidebar and are unaware of the restrictions which setting the <browser> type to content imposes upon the code you load into the <browser>.
      • It looks like you are trying to access the currant tab's URL from the code in your <browser>. You should consider restructuring your code such that access to chrome privileges is not required from the content that is in the <browser>. In other words, if possible, you should write your code such that you do not need to have access to the URL, or other information, directly from code running in the <browser> you insert. However, doing so may end up being significantly more complex.

    Getting access to chrome privileges within the <browser> you are adding:

    Setting the <browser> type to content is for:

    A browser for content. The content that is loaded inside the browser is not allowed to access the chrome above it.

    Setting the <browser> type to chrome is for:

    (default behaviour): A browser, intended to be used for loading privileged content using a chrome:// URI. Don't use for content from web, as this may cause serious security problems!

    To set the the <browser> type to chrome you can do:

    var propsToSet = {
        id: 'demo-sidebar-with-html_browser',
        type: 'chrome', //<---------------------------This
    

    The code provided by Noitidart comes close to what you need. However, it needs to be slightly modified, as gBrowser.currentURI is a nsIURI, not a string). Thus, you need to use gBrowser.currentURI.spec. You can get it working by changing your makeHTML() to:

    function makeHTML(value, key){
        var arrValues = value.split(",");
        var htmlContent = '<html><head></head>'
            + '<body style="background-color: white;">'
            + '  <div name="content" class="content">'
            + '    <p> Name :' + arrValues[0] + '</p>';
        htmlContent += '<p> Price :' + arrValues[2] + '</p>';
        htmlContent += '<p> Color :' + arrValues[3] + '</p>';
        htmlContent += '<p> UID :' + arrValues[1] + '</p>';
        htmlContent += 
              '    <p><input type="radio" name="valid" value="yes" />Yes &nbsp;&nbsp; '
            + '    <input type="radio" name="valid" value="no" /> No</p>'
            + '    <p><input type="text" placeholder="Reason" name="checkreason" /></p>'
            + '    <p><input type="text" placeholder="Feedback" name="feedback" /></p>'
            + '  </div>'
            + '  <div>'
            + '    <button name="load" type="button" id="loadit" onclick="loadHtml()" '
            + '        loadurl="' + arrValues[4] + '">Load</button>'
            + '    <button name="save" type="button">Save </button>'
            + '    <button name="next" type="button" key="' + key + '">Next </button>'
            + '  </div>'
            + '  <script>'
            + '    function loadHtml() {' 
            + '      const Cu = Components.utils;'
            + '      Cu.import("resource://gre/modules/Services.jsm");'
            + '      var win = Services.wm.getMostRecentWindow("navigator:browser");'
            + '      if (win) {'
            + '        var url = win.gBrowser.currentURI.spec;'
            + '        alert("URL=" + url);'
            + '      }'
            + '    }'
            + '  </script>'
            + '</body></html>';
        return htmlContent;
    }
    

    Security concerns:
    There are some really significant security concerns that you may be running into. Just from the fact that you want the URL for the current page implies that you may be rapidly approaching where these are a real issue. It is going to depend on what exactly you are going to do. Given that you are not providing that information, I am not going to go too in depth as to what the issues are. However, you should read "Firefox Security Basics for Developers" and "Security best practices in extensions"

    Additional information about sidebars:
    You should seriously consider creating an Add-on SDK based add-on, instead of a bootstrap one, and using the ui/sidebar API.

    If you are interested, I created some code which can be used to create a "sidebar" that is located at the top, left, right, or bottom of the active tab, or in a separate window (similar to what the devtools panel will do). The sidebar created is associated with the currently active tab, not all tabs (I guess I should have made it an option to have it associated with all tabs, or just the current tab) in my answer to: "How to create a bottom-docked panel with XUL in Firefox?" and "Firefox Extension, Window related sidebar"