On many websites (Dropbox being a good example), when you click on a document to download it, it opens a new window/tab, then the download prompt appears, and the tab/window immediately closes itself (while the prompt remains open).
How do I replicate this behavior using javascript?
I think one approach would be to detect the appearance of that download prompt, then use window.close()
. However, I'm not sure how to detect that particular prompt.
A cross-browser solution is preferred, but anything that'll work in Firefox is greatly appreciated.
Clarifications
More clarification: With regards to the type of website mentioned in clarification 2 above, what I want to do is click on the download link, have that particular download page load in a new window, and have the window close once the download has been initiated.
There are three basic parts to what you want:
target="_blank"
. The tab must be opened with javascript, so that javascript will be allowed to close it when the time comes.For this discussion, refer to this test page at jsFiddle. It is structured like this:
<div id="downloadLinks">
<ul>
<li><a class="dwnPageLink" href="http://fiddle.jshell.net/cDTKj/show/">
Test file, download page at jsFiddle
</a>
</li>
<li><a class="dwnPageLink" href="http://dw.com.com/redir...">
TextPad (a great text editor), download page at CNET / Download
</a>
</li>
</ul>
</div>
where the a.dwnPageLink
links each open a "Download page" -- which automatically starts a file download after a short delay.
a.dwnPageLink
):We intercept the links like so:
$("#downloadLinks a.dwnPageLink").each (interceptLink);
function interceptLink (index, node) {
var jNode = $(node);
jNode.click (openInNewTab);
jNode.addClass ("intercepted");
}
Note that we also add a CSS class, so that we may quickly see which links have been affected.
openInNewTab
will be detailed below. It must both open a tab, and it must stop the normal link action.
We must use window.open()
to handle the link. If the page is not opened via window.open
, our script will not be able to close it.
Note that GM_openInTab()
cannot be used because it does not cause window.opener
to be set properly and otherwise does not provide a mechanism to close the opened tab.
The new tab is launched in openInNewTab
, which looks like this:
function openInNewTab (zEvent) {
//-- Optionally adjust the href here, if needed.
var targURL = this.href;
var newTab = window.open (targURL, "_blank");
//--- Stop the link from doing anything else.
zEvent.preventDefault ();
zEvent.stopPropagation ();
return false;
}
It is not possible to monitor for the File dialog from the launching page. So we must set the script to also run on the "popup" tab. Add @include
directives accordingly.
The popup portion of our script can detect the File dialog by monitoring the beforeunload
event. Browsers will fire the beforeunload
event, just before opening the File dialog (and also just before the tab closes, but we can ignore that).
However, we cannot just close the tab when the dialog appears. Doing so will close the dialog too. So we add a small time delay, and a "Confirm" dialog so that the tab will stay open until the File dialog is closed. To clear the Confirm dialog, just hit Enter an extra time (or Click OK).
The code looks like this:
$(window).bind ("beforeunload", function (zEvent) {
//-- Allow time for the file dialog to actually open.
setTimeout ( function () {
/*-- Since the time it takes for the user to respond
to the File dialog can vary radically, use a confirm
to keep the File dialog open long enough for the user
to act.
*/
var doClose = confirm ("Close this window?");
if (doClose) {
window.close ();
}
},
444 // 0.444 seconds
);
} );
window.opener
. On a page opened by javascript, this will have a non-null value.This script works on the test page and on CNET / Download pages:
// ==UserScript==
// @name _Download page, auto closer
// @namespace _pc
// ******** Includes for "List pages" that have the links we might click...
// @include http://YOUR_SERVER.COM/YOUR_LIST-PAGE_PATH/*
// @include http://jsbin.com/ozofom/*
// @include http://fiddle.jshell.net/qy3NP/*
// @include http://download.cnet.com/*
// ******** Includes for "Popup pages" that do the actual downloads...
// @include http://YOUR_SERVER.COM/YOUR_POPUP-PAGE_PATH/*
// @include http://fiddle.jshell.net/cDTKj/*
// @include http://dw.com.com/redir?*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @grant GM_addStyle
// @grant GM_openInTab
// ==/UserScript==
/*- Important: The @include or @match directives must for for both the pages
that list the download links, AND the pages that do the actual downloading.
The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var bPageNotOpenedByJavascript = window.opener ? false : true;
if (bPageNotOpenedByJavascript) {
/***** "Normal" page, which might contain links to special download pages.
*/
//--- Intercept links to the download pages:
// For our jsFiddle Test page:
$("#downloadLinks a.dwnPageLink").each (interceptLink);
// For CNET/Download:
$("#downloadLinks div.dlLinkWrapper a").each (interceptLink);
GM_addStyle ( " \
a.intercepted { \
background: lime; \
} \
" );
}
else {
/***** Page opened by JS in either a popup or new tab.
This was *most likely* done by us, using window.open.
*/
$(window).bind ("beforeunload", function (zEvent) {
//-- Allow time for the file dialog to actually open.
setTimeout ( function () {
/*-- Since the time it takes for the user to respond
to the File dialog can vary radically, use a confirm
to keep the File dialog open long enough for the user
to act.
*/
var doClose = confirm ("Close this window?");
if (doClose) {
window.close ();
}
},
444 // 0.444 seconds
);
} );
}
function interceptLink (index, node) {
var jNode = $(node);
jNode.click (openInNewTab);
jNode.addClass ("intercepted");
}
function openInNewTab (zEvent) {
//-- Optionally adjust the href here, if needed.
var targURL = this.href;
var newTab = window.open (targURL, "_blank");
//--- Stop the link from doing anything else.
zEvent.preventDefault ();
zEvent.stopPropagation ();
return false;
}