32

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'
  });
}
gkalpak
  • 47,844
  • 8
  • 105
  • 118
Arvind Anandala
  • 539
  • 2
  • 7
  • 15

2 Answers2

18

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.)

gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • Thanks for your reply I am working on to integrate your code. And i will let you know the update – Arvind Anandala Jan 15 '14 at 16:15
  • I integrated your code in my project. when click on "Browse and upload" button it is prompting two windows(one window is fixed with extension icon(plugin) and another is normal ) to pick a file . Normal window is coming over the window, Which is fixed to the extension icon(plugin). But when i click on "close" or "open" Button in normal window.Both Windows are closing – Arvind Anandala Jan 17 '14 at 07:52
  • That is definitely nor caused by my code :) I can't tell what is wrong though, if I don't see the code. – gkalpak Jan 17 '14 at 08:04
  • OK. If you would like to post the code somewhere, I could take a look if you want me to. – gkalpak Jan 17 '14 at 10:34
  • @ExpertSystem Hello, I upvote your answer as it make sense to me, but I have created the extension from your code and when i click the button , nothing happens, why ? chrome extension still support that ? – Jigberto Jun 13 '14 at 08:08
  • @Jigberto: I haven't tested the solution above lately, but it sure was working on Jan 15 :) What platform are you on ? – gkalpak Jun 13 '14 at 08:22
  • @ExpertSystem My platform is Windows 7, Google Chrome 35.0.1916.114 m – Jigberto Jun 13 '14 at 08:28
  • @ExpertSystem I have posted a question, i need to select a file and load it with FileReader() in background script. I'm not sure how to do that, maybe you can help me, please check my question. thanks. http://stackoverflow.com/questions/24193578/pass-input-file-to-background-script – Jigberto Jun 13 '14 at 08:31
  • I hit same issue as https://stackoverflow.com/questions/23842981/file-input-in-chrome-extension-popup# – gvijay Jun 27 '14 at 08:32
  • @Jigberto: I tested the code above and it doesn't seem to work (on Windows 8 at least). Basically, Chrome seems to not allow the `.click()` method, considering it programmatic rather than user-initiated. (I am not sure if it is intended behavior or a newly introduced bug.) Anyway, since you are on Windows, why not place the file-loading logic in the popup's JS ? The above solution was intended as a work-around for a known bug in OSX. – gkalpak Jun 27 '14 at 13:55
  • @gvijay: What platform are you on ? What exactly is your usecase ? – gkalpak Jun 27 '14 at 13:57
  • @ExpertSystem I was about to reply here, but then I saw your comment on the referenced question. Seems like its a chrome ubuntu bug. – gvijay Jun 30 '14 at 08:47
  • @ExpertSystem ty, your code works fine on Windowns 7, but dont on Mac OS (file dialog dont open) – Luciuz Jul 18 '14 at 12:19
  • 3
    Hi! I tried this solution, under windows 7 (chrome ver. 37) and the .click event is not triggered. (Same as @ExpertSystem commented). I also tried to put the file loading logic directly inside the popup window (window.html/js) but the popup window closes as soon as I click the button. It only stays up (and correctly executes the file loading) if I keep the developer tools window open (right click on the plugin icon -> Inspect popup). Any ideas/solutions? – visoft Aug 27 '14 at 17:03
  • I'm having an issue with the position of the dialog window using this solution. Please take a look if you can :) http://stackoverflow.com/questions/32570714/chrome-extension-file-upload-browse-window-position-off-screen – CaitlinHavener Sep 14 '15 at 17:44
  • @CaitlinHavener: Sorry, I can't help. I have no idea what it might be going on (nor can I reproduce the issue). – gkalpak Sep 14 '15 at 17:56
  • It doesn't! I'm on OSX El Captain and the click event is not triggered. The file input works in the popup but the annoying Chrome bug that opens the dialog with the open button hidden :( – Pablo Ezequiel Leone Aug 02 '16 at 21:00
  • Guys, this answer is 2.5 years old (and counting). Browsers and OSes (fortunately) change. Something that was an issue 2.5 years back is likely not an issue any more and something that solved a problem 2.5 year back might not be helpful today. This did work when it was posted. – gkalpak Aug 03 '16 at 04:55
10

ExpertSystem's solution didn't work for me as it would not let me call click on the element in the background script, but I came up with a workaround using much of his code. If you don't have an issue slightly tainting the current tab, put his background.js code in a content script with the proper message passing wrappers. Most of the credit goes to ExpertSystem, I merely shuffled things around.

Background:

The problem I needed to solve was that I wanted to allow a JSON file to be uploaded and parsed into my extension via the popup. The workaround I came up with for doing so required an intricate dance of all three pieces; the popup, background, and content scripts.

popup.js

// handler for import button
// sends a message to the content script to create the file input element and click it
$('#import-button').click(function() {
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
        chrome.tabs.sendMessage(tabs[0].id, {message: "chooseFile"}, function(response) {
            console.log(response.response);
        });
    });
});

content.js

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    if (request.message == "chooseFile") {
        /* Creates an `input[type="file]` */
        var fileChooser = document.createElement('input');
        fileChooser.type = 'file';

        fileChooser.addEventListener('change', function () {
            console.log("file change");
            var file = fileChooser.files[0];

            var reader = new FileReader();
            reader.onload = function(){
                var data = reader.result;
                fields = $.parseJSON(data);
                // now send the message to the background
                chrome.runtime.sendMessage({message: "import", fields: fields}, function(response) {
                    console.log(response.response);
                });
            };
            reader.readAsText(file);
            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);

        fileChooser.click();
        sendResponse({response: "fileChooser clicked"});
    }

});

background.js

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    if (request.message == "import") {
        fields = request.fields; // use the data
        sendResponse({response: "imported"});
    }
});

The reason this works while the other may or may not, is because the file input element is created within the scope of the current tab, which persists throughout the entire process.

RayfenWindspear
  • 6,116
  • 1
  • 30
  • 42
  • Hi, this was an awesome suggestion, but is there anyway to prevent the popup from closing automatically? it is loosing focus and getting closed after the user chooses a file or closes the file chooser window. – tinyCoder Jul 03 '19 at 17:42
  • 1
    @tinyCoder It is impossible to keep the popup open. This is just a limitation of extensions. My manager set me to the task of keeping it open for like a week. I even reached out to support. This is the way it is designed. It just isn't possible. The ONLY exception being that if you open the devtools (right click inspect), then it will stay open, but that isn't a practical workaround. – RayfenWindspear Jul 04 '19 at 15:32