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' }); } 

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

18 Comments

Thanks for your reply I am working on to integrate your code. And i will let you know the update
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
That is definitely nor caused by my code :) I can't tell what is wrong though, if I don't see the code.
OK. If you would like to post the code somewhere, I could take a look if you want me to.
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?
|
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.

2 Comments

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.