Search code examples
javascriptgoogle-chromegoogle-chrome-extension

Upload File as a Form Data through chrome extension


I am uploading a file through chrome extension as a form data and my code follows below. The problem here is that the file browsing window opens for just a second and then disappears.
The issue appears in Mac OS only.

manifest.json:

"background": {
  "scripts": ["jszip.js", "background.js"]
},

background.js:

chrome.runtime.onMessage.addListener(function (msg) {
  if (msg.action === 'browse')
  {
    var myForm=document.createElement("FORM");
    var myFile=document.createElement("INPUT");
    myFile.type="file";
    myFile.id="selectFile";
    //myFile.onclick="openDialog()";
    myForm.appendChild(myFile);
    var myButton=document.createElement("INPUT");
    myButton.name="submit";
    myButton.type="submit";
    myButton.value="Submit";
    myForm.appendChild(myButton);
    document.body.appendChild(myForm);
  }
});

popup.js:

window.onload = function () {
  chrome.runtime.sendMessage({
    action: 'browse'
  });
}

Solution

  • A little "background story":

    You want to let the user choose and upload a file from your popup. But in OSX, as soon as the file-chooser dialog opens, the popup loses focus and closes, causing its JS context to get destroyed as well. Thus, the dialog opens and closes immediately.

    This is a known bug on MAC for quite some time.


    The solution:

    You can move the dialog opening logic to the background-page, which is not affected by loss of focus. From the popup, you can send a message to the background-page, requesting to initiate the browse-and-upload process (see sample code below).

    manifest.json

    {
        ...
        "background": {
            "persistent": false,
            "scripts": ["background.js"]
        },
    
        "browser_action": {
            "default_title": "Test Extension",
    //        "default_icon": {
    //            "19": "img/icon19.png",
    //            "38": "img/icon38.png"
    //        },
            "default_popup": "popup.html"
        },
    
        "permissions": [
            "https://www.example.com/uploads"
            // The above permission is needed for cross-domain XHR
        ]
    }
    

    popup.html

        ...
        <script src="popup.js"></script>
    </head>
    <body>
        <input type="button" id="button" value="Browse and Upload" />
        ...
    

    popup.js

    document.addEventListener('DOMContentLoaded', function () {
        document.getElementById('button').addEventListener('click', function () {
            chrome.runtime.sendMessage({ action: 'browseAndUpload' });
            window.close();
        });
    });
    

    background.js

    var uploadUrl = 'https://www.example.com/uploads';
    
    /* Creates an `input[type="file]` */
    var fileChooser = document.createElement('input');
    fileChooser.type = 'file';
    fileChooser.addEventListener('change', function () {
        var file = fileChooser.files[0];
        var formData = new FormData();
        formData.append(file.name, file);
    
        var xhr = new XMLHttpRequest();
        xhr.open('POST', uploadUrl, true);
        xhr.addEventListener('readystatechange', function (evt) {
            console.log('ReadyState: ' + xhr.readyState,
                        'Status: ' + xhr.status);
        });
    
        xhr.send(formData);
        form.reset();   // <-- Resets the input so we do get a `change` event,
                        //     even if the user chooses the same file
    });
    
    /* Wrap it in a form for resetting */
    var form = document.createElement('form');
    form.appendChild(fileChooser);
    
    /* Listen for messages from popup */
    chrome.runtime.onMessage.addListener(function (msg) {
        if (msg.action === 'browseAndUpload') {
            fileChooser.click();
        }
    });
    

    Heads up:
    As a security precaution, Chrome will execute fileChooser.click() only if it is a result of user interaction.
    In the above example, the user clicks the button in the popup, which sends a message to the background-page, which calls fileChooser.click();. If you try to call it programmatically it won't work. (E.g. calling it on document load won't have any effect.)