Search code examples
contextmenuxulrunner

How to add a copy/paste context menu to browser element in XULRunner?


I would like to allow the user of my XULRunner based app to be able to do copy/paste via a context menu. Keyboard shortcuts Ctrl-C and Ctrl-V are already working fine


Solution

  • Here it is without flash. The getInputSelection function is from here: Is there an Internet Explorer approved substitute for selectionStart and selectionEnd?.

    <?xml version="1.0"?>
    
    <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
    
    <window id="mywin" title="my app" 
        width="800" height="600" persist="screenX screenY width height sizemode" 
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    
        <popupset>
            <menupopup id="clipmenu">
          <menuitem label="Copy" oncommand="copy()"/>
          <menuitem label="Paste" oncommand="paste();"/>
            </menupopup>
        </popupset>
    
        <browser
              type="content-primary"
          src="http://127.0.0.1/"
          flex="1"
          disablehistory="true"
          id="browserId"
          context="clipmenu"
          />
    <script>
    <![CDATA[
    
        function copy()
    {
        var tabBrowser = document.getElementById("browserId");
        var selectedTagName = tabBrowser.contentWindow.document.activeElement.tagName;
        var windowObj;
        if(selectedTagName == "IFRAME")
    {
      windowObj = tabBrowser.contentWindow.frames[tabBrowser.contentWindow.document.activeElement.name]; 
    }
    else
    {
        windowObj = document.getElementById("browserId").contentWindow;
    }
    
    var selectedText = windowObj.getSelection();
    
    if(!selectedText || selectedText == "")
    {
        var focused = windowObj.document.activeElement;
        if(focused && focused.value)
        {
            selectedText = getSelectionFromInput(focused);
        }
    }
    
    //alert(selectedText + "---");
    
    const clipHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
        clipHelper.copyString(selectedText);
    }
    
    function getSelectionFromInput(focused)
    {
    var focusedValue = focused.value;
    var sel = getInputSelection(focused);
    var selectedText = "";
    if(focusedValue.length == (sel.end))
    {
        selectedText = focusedValue.substring(sel.start);
    }
    else
    {
        selectedText = focusedValue.substring(sel.start, (sel.end));
    }
    
    return selectedText;
    }
    
      function paste()
      {
            var clip = Components.classes["@mozilla.org/widget/clipboard;1"].getService(Components.interfaces.nsIClipboard); 
        var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable); 
            trans.addDataFlavor("text/unicode"); 
            clip.getData(trans, clip.kGlobalClipboard); 
            var str = new Object(); 
            var len = new Object(); 
            trans.getTransferData("text/unicode",str,len); 
            str = str.value.QueryInterface(Components.interfaces.nsISupportsString); 
            str = str.data.substring(0, len.value / 2);
    
            var focused = document.commandDispatcher.focusedElement;
    var focusedValue = focused.value; 
    var sel = getInputSelection(focused);
    focused.value = focusedValue.substring(0,sel.start) + str + focusedValue.substring(sel.end);
     }
    
    function getInputSelection(el) {
    var start = 0, end = 0, normalizedValue, range,
        textInputRange, len, endRange;
    
    if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
        start = el.selectionStart;
        end = el.selectionEnd;
    } else {
        range = document.selection.createRange();
    
        if (range && range.parentElement() == el) {
            len = el.value.length;
            normalizedValue = el.value.replace(/\r\n/g, "\n");
    
            // Create a working TextRange that lives only in the input
            textInputRange = el.createTextRange();
            textInputRange.moveToBookmark(range.getBookmark());
    
            // Check if the start and end of the selection are at the very end
            // of the input, since moveStart/moveEnd doesn't return what we want
            // in those cases
            endRange = el.createTextRange();
            endRange.collapse(false);
    
            if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
                start = end = len;
            } else {
                start = -textInputRange.moveStart("character", -len);
                start += normalizedValue.slice(0, start).split("\n").length - 1;
    
                if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
                    end = len;
                } else {
                    end = -textInputRange.moveEnd("character", -len);
                    end += normalizedValue.slice(0, end).split("\n").length - 1;
                }
            }
        }
    }
    
    return {
        start: start,
        end: end
    };
    }
    ]]>
    </script>   
    </window>
    

    Updated to support iframes.